├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .simplecov ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── DOCUMENTING.md ├── Gemfile ├── HISTORY.md ├── LICENSE ├── README.md ├── Rakefile ├── acceptance ├── config │ ├── acceptance-options.rb │ ├── base │ │ └── acceptance-options.rb │ ├── hypervisor │ │ └── acceptance-options.rb │ └── subcommands │ │ └── acceptance-options.rb ├── fixtures │ ├── README.md │ ├── files │ │ ├── failing_shell_script.txt │ │ ├── retry_script.txt │ │ ├── shell_script_with_output.txt │ │ └── simple_text_file.txt │ ├── module │ │ ├── Gemfile │ │ ├── README.md │ │ ├── Rakefile │ │ ├── lib │ │ │ └── empty.txt │ │ ├── manifests │ │ │ └── init.pp │ │ ├── metadata.json │ │ ├── spec │ │ │ ├── acceptance │ │ │ │ ├── demo_spec.rb │ │ │ │ └── nodesets │ │ │ │ │ ├── centos-59-x64.yml │ │ │ │ │ ├── centos-64-x64-pe.yml │ │ │ │ │ ├── centos-64-x64.yml │ │ │ │ │ ├── centos-65-x64.yml │ │ │ │ │ ├── default.yml │ │ │ │ │ ├── internal-vpool.yml │ │ │ │ │ ├── sles-11-x64.yml │ │ │ │ │ ├── ubuntu-server-10044-x64.yml │ │ │ │ │ ├── ubuntu-server-12042-x64.yml │ │ │ │ │ ├── ubuntu-server-1404-x64.yml │ │ │ │ │ └── ubuntu-server-14042-x64.yml │ │ │ ├── classes │ │ │ │ └── init_spec.rb │ │ │ ├── spec_helper.rb │ │ │ └── spec_helper_acceptance.rb │ │ ├── tests │ │ │ └── init.pp │ │ └── vendor │ │ │ └── bundle │ │ │ └── ruby │ │ │ └── gems.txt │ └── package │ │ ├── deb │ │ ├── pl-puppetserver-latest-jessie.list │ │ ├── pl-puppetserver-latest-lucid.list │ │ ├── pl-puppetserver-latest-precise.list │ │ ├── pl-puppetserver-latest-squeeze.list │ │ ├── pl-puppetserver-latest-trusty.list │ │ ├── pl-puppetserver-latest-vivid.list │ │ ├── pl-puppetserver-latest-wheezy.list │ │ ├── pl-puppetserver-latest-wily.list │ │ └── pl-puppetserver-latest-xenial.list │ │ └── rpm │ │ ├── pl-puppetserver-latest-repos-pe-el-6-i386.repo │ │ ├── pl-puppetserver-latest-repos-pe-el-6-x86_64.repo │ │ ├── pl-puppetserver-latest-repos-pe-el-7-i386.repo │ │ ├── pl-puppetserver-latest-repos-pe-el-7-x86_64.repo │ │ └── pl-puppetserver-latest-repos-pe-sles-12-x86_64.repo ├── lib │ └── helpers │ │ └── test_helper.rb ├── pre_suite │ ├── README.md │ ├── pe │ │ └── install.rb │ └── subcommands │ │ ├── 05_install_ruby.rb │ │ └── 08_install_beaker.rb ├── scripts │ └── all_but_host_test.sh └── tests │ ├── base │ ├── README.md │ ├── dsl │ │ ├── helpers │ │ │ ├── configuration_test.rb │ │ │ ├── hocon_helpers_test.rb │ │ │ └── host_helpers │ │ │ │ ├── add_system32_hosts_entry_test.rb │ │ │ │ ├── archive_file_from_test.rb │ │ │ │ ├── backup_the_file_test.rb │ │ │ │ ├── check_for_package_test.rb │ │ │ │ ├── create_remote_file_test.rb │ │ │ │ ├── curl_on_test.rb │ │ │ │ ├── curl_with_retries_test.rb │ │ │ │ ├── echo_on_test.rb │ │ │ │ ├── install_package_test.rb │ │ │ │ ├── on_test.rb │ │ │ │ ├── retry_on_test.rb │ │ │ │ ├── rsync_to_test.rb │ │ │ │ ├── run_cron_on_test.rb │ │ │ │ ├── run_script_on_test.rb │ │ │ │ ├── run_script_test.rb │ │ │ │ ├── scp_from_test.rb │ │ │ │ ├── scp_to_test.rb │ │ │ │ ├── shell_test.rb │ │ │ │ └── upgrade_package_test.rb │ │ ├── platform_tag_confiner_test.rb │ │ └── structure_test.rb │ ├── host │ │ ├── file_test.rb │ │ ├── group_test.rb │ │ ├── host_test.rb │ │ ├── packages.rb │ │ ├── packages_unix.rb │ │ └── user_test.rb │ ├── host_prebuilt_steps │ │ └── ssh_environment_test.rb │ └── test_suite │ │ └── export.rb │ ├── hypervisor │ └── communication_test.rb │ ├── install │ └── from_file.rb │ ├── load_path_bootstrap.rb │ └── subcommands │ ├── destroy.rb │ ├── exec.rb │ ├── init.rb │ └── provision.rb ├── beaker.gemspec ├── bin └── beaker ├── docs ├── README.md ├── concepts │ ├── argument_processing_and_precedence.md │ ├── beaker_libraries.md │ ├── beaker_vs_beaker_rspec.md │ ├── glossary.md │ ├── masterless_puppet.md │ ├── roles_what_are_they.md │ ├── shared_options_for_executing_beaker_commands.md │ ├── style_guide.md │ ├── test_tagging.md │ ├── testing_beaker_itself.md │ ├── ticket_process.md │ └── types_puppet_4_and_the_all_in_one_agent.md ├── how_to │ ├── archive_sut_files.md │ ├── change_terminal_output_coloring.md │ ├── cloning_private_repos.md │ ├── confine.md │ ├── debug_beaker_tests.md │ ├── enabling_cross_sut_access.md │ ├── hosts │ │ ├── README.md │ │ └── archlinux.md │ ├── hypervisors │ │ ├── README.md │ │ └── solaris.md │ ├── install_puppet.md │ ├── platform_specific_tag_confines.md │ ├── preserve_hosts.md │ ├── rake_tasks.md │ ├── recipes.md │ ├── run_in_parallel.md │ ├── ssh_agent_forwarding.md │ ├── ssh_connection_preference.md │ ├── test_arbitrary_beaker_versions.md │ ├── the_beaker_dsl.md │ ├── upgrade_from_2_to_3.md │ ├── upgrade_from_3_to_4.md │ ├── use_hocon_helpers.md │ ├── use_user_password_authentication.md │ └── write_a_beaker_test_for_a_module.md └── tutorials │ ├── README.md │ ├── creating_a_test_environment.md │ ├── how_to_beaker.md │ ├── installation.md │ ├── lets_write_a_test.md │ ├── quick_start_rake_tasks.md │ ├── subcommands.md │ ├── test_run.md │ ├── test_suites.md │ └── the_command_line.md ├── ext └── completion │ └── beaker-completion.bash ├── lib ├── beaker.rb └── beaker │ ├── cli.rb │ ├── command.rb │ ├── command_factory.rb │ ├── dsl.rb │ ├── dsl │ ├── assertions.rb │ ├── helpers.rb │ ├── helpers │ │ ├── hocon_helpers.rb │ │ ├── host_helpers.rb │ │ ├── test_helpers.rb │ │ └── web_helpers.rb │ ├── install_utils.rb │ ├── outcomes.rb │ ├── patterns.rb │ ├── roles.rb │ ├── structure.rb │ ├── test_tagging.rb │ └── wrappers.rb │ ├── host.rb │ ├── host │ ├── aix.rb │ ├── aix │ │ ├── exec.rb │ │ ├── file.rb │ │ ├── group.rb │ │ └── user.rb │ ├── freebsd.rb │ ├── freebsd │ │ ├── exec.rb │ │ └── pkg.rb │ ├── mac.rb │ ├── mac │ │ ├── exec.rb │ │ ├── group.rb │ │ ├── pkg.rb │ │ └── user.rb │ ├── pswindows.rb │ ├── pswindows │ │ ├── exec.rb │ │ ├── file.rb │ │ ├── group.rb │ │ ├── pkg.rb │ │ └── user.rb │ ├── unix.rb │ ├── unix │ │ ├── exec.rb │ │ ├── file.rb │ │ ├── group.rb │ │ ├── pkg.rb │ │ └── user.rb │ ├── windows.rb │ └── windows │ │ ├── exec.rb │ │ ├── file.rb │ │ ├── group.rb │ │ ├── pkg.rb │ │ └── user.rb │ ├── host_prebuilt_steps.rb │ ├── hypervisor.rb │ ├── hypervisor │ └── noop.rb │ ├── junit.xsl │ ├── local_connection.rb │ ├── logger.rb │ ├── logger_junit.rb │ ├── network_manager.rb │ ├── options.rb │ ├── options │ ├── command_line_parser.rb │ ├── hosts_file_parser.rb │ ├── options_file_parser.rb │ ├── options_hash.rb │ ├── parser.rb │ ├── presets.rb │ ├── subcommand_options_file_parser.rb │ └── validator.rb │ ├── perf.rb │ ├── platform.rb │ ├── result.rb │ ├── shared.rb │ ├── shared │ ├── error_handler.rb │ ├── fog_credentials.rb │ ├── host_manager.rb │ ├── options_resolver.rb │ ├── repetition.rb │ ├── semvar.rb │ └── timed.rb │ ├── ssh_connection.rb │ ├── subcommand.rb │ ├── subcommands │ └── subcommand_util.rb │ ├── tasks │ ├── quick_start.rb │ ├── rake_task.rb │ └── test.rb │ ├── test_case.rb │ ├── test_suite.rb │ ├── test_suite_result.rb │ └── version.rb ├── rubocop.yml └── spec ├── beaker ├── cli_spec.rb ├── command_spec.rb ├── dsl │ ├── assertions_spec.rb │ ├── helpers │ │ ├── host_helpers_spec.rb │ │ ├── test_helpers_spec.rb │ │ └── web_helpers_spec.rb │ ├── outcomes_spec.rb │ ├── roles_spec.rb │ ├── structure_spec.rb │ ├── test_tagging_spec.rb │ └── wrappers_spec.rb ├── host │ ├── aix_spec.rb │ ├── freebsd │ │ ├── exec_spec.rb │ │ └── pkg_spec.rb │ ├── mac │ │ ├── exec_spec.rb │ │ ├── group_spec.rb │ │ ├── pkg_spec.rb │ │ └── user_spec.rb │ ├── pswindows │ │ ├── exec_spec.rb │ │ ├── file_spec.rb │ │ └── user_spec.rb │ ├── pswindows_spec.rb │ ├── unix │ │ ├── exec_spec.rb │ │ ├── file_spec.rb │ │ └── pkg_spec.rb │ ├── unix_spec.rb │ ├── windows │ │ ├── exec_spec.rb │ │ ├── file_spec.rb │ │ ├── group_spec.rb │ │ ├── pkg_spec.rb │ │ └── user_spec.rb │ └── windows_spec.rb ├── host_prebuilt_steps_spec.rb ├── host_spec.rb ├── hypervisor │ └── hypervisor_spec.rb ├── localhost_connection_spec.rb ├── logger_junit_spec.rb ├── logger_spec.rb ├── network_manager_spec.rb ├── options │ ├── command_line_parser_spec.rb │ ├── data │ │ ├── badyaml.cfg │ │ ├── hosts.cfg │ │ ├── hosts_preserved.yml │ │ └── opts.txt │ ├── hosts_file_parser_spec.rb │ ├── options_file_parser_spec.rb │ ├── options_hash_spec.rb │ ├── parser_spec.rb │ ├── presets_spec.rb │ ├── subcommand_options_parser_spec.rb │ └── validator_spec.rb ├── perf_spec.rb ├── platform_spec.rb ├── shared │ ├── error_handler_spec.rb │ ├── fog_credentials_spec.rb │ ├── host_manager_spec.rb │ ├── options_resolver_spec.rb │ ├── repetition_spec.rb │ └── semvar_spec.rb ├── ssh_connection_spec.rb ├── subcommand │ └── subcommand_util_spec.rb ├── subcommand_spec.rb ├── test_case_spec.rb └── test_suite_spec.rb ├── helpers.rb ├── matchers.rb ├── mocks.rb └── spec_helper.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # raise PRs for gem updates 4 | - package-ecosystem: bundler 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "13:00" 9 | open-pull-requests-limit: 10 10 | 11 | # Maintain dependencies for GitHub Actions 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | time: "13:00" 17 | open-pull-requests-limit: 10 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-24.04 12 | if: github.repository_owner == 'voxpupuli' 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Install Ruby 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: '3.4' 19 | env: 20 | BUNDLE_WITHOUT: release:development:rubocop 21 | - name: Build gem 22 | run: gem build --strict --verbose *.gemspec 23 | - name: Publish gem to rubygems.org 24 | run: gem push *.gem 25 | env: 26 | GEM_HOST_API_KEY: '${{ secrets.RUBYGEMS_AUTH_TOKEN }}' 27 | - name: Setup GitHub packages access 28 | run: | 29 | mkdir -p ~/.gem 30 | echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials 31 | chmod 0600 ~/.gem/credentials 32 | - name: Publish gem to GitHub packages 33 | run: gem push --key github --host https://rubygems.pkg.github.com/voxpupuli *.gem 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test 3 | 4 | on: 5 | pull_request: {} 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | rubocop_and_matrix: 12 | env: 13 | BUNDLE_WITHOUT: release 14 | runs-on: ubuntu-24.04 15 | outputs: 16 | ruby: ${{ steps.ruby.outputs.versions }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install Ruby ${{ matrix.ruby }} 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: "3.4" 23 | bundler-cache: true 24 | - name: Run Rubocop 25 | run: bundle exec rake rubocop 26 | - id: ruby 27 | uses: voxpupuli/ruby-version@v1 28 | 29 | test: 30 | name: "Ruby ${{ matrix.ruby }}" 31 | runs-on: ubuntu-24.04 32 | needs: rubocop_and_matrix 33 | strategy: 34 | fail-fast: false 35 | matrix: 36 | ruby: ${{ fromJSON(needs.rubocop_and_matrix.outputs.ruby) }} 37 | env: 38 | BUNDLE_WITHOUT: release:rubocop 39 | BEAKER_HYPERVISOR: docker 40 | steps: 41 | - uses: actions/checkout@v4 42 | - name: Install Ruby ${{ matrix.ruby }} 43 | uses: ruby/setup-ruby@v1 44 | with: 45 | ruby-version: ${{ matrix.ruby }} 46 | bundler-cache: true 47 | - name: Build gem 48 | run: gem build --strict --verbose *.gemspec 49 | - name: Run unit tests 50 | run: bundle exec rake spec 51 | - name: Run acceptance tests 52 | run: bundle exec rake acceptance 53 | 54 | tests: 55 | needs: 56 | - rubocop_and_matrix 57 | - test 58 | runs-on: ubuntu-24.04 59 | name: Test suite 60 | steps: 61 | - run: echo Test suite completed 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | 3 | # Gem structure 4 | junit 5 | acceptance-tests 6 | pkg 7 | Gemfile.lock 8 | 9 | # Beaker-generated & local testing 10 | test.cfg 11 | options.rb 12 | merged_options.rb 13 | tmp/ 14 | log/ 15 | sut-files.tgz 16 | 17 | # YARD 18 | .yardoc 19 | yard_docs 20 | doc 21 | 22 | # Dependencies 23 | # NOTE: For consistency, please use `vendor/bundle` for Bundler-installed dependencies 24 | .bundle 25 | .vendor 26 | _vendor 27 | vendor 28 | 29 | # Byebug 30 | .byebugrc 31 | .byebug_history 32 | 33 | # SimpleCov 34 | coverage 35 | 36 | 37 | # 38 | # Editor/IDE Clutter 39 | # 40 | # (TODO: consider removing these; use local personal .gitignore instead) 41 | 42 | # Vim 43 | *.swp 44 | # JetBrains IDEA 45 | *.iml 46 | .idea/ 47 | # rbenv file 48 | .ruby-version 49 | .ruby-gemset 50 | # Vagrant folder 51 | .vagrant/ 52 | .vagrant_files/ 53 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --tty 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | inherit_from: .rubocop_todo.yml 3 | 4 | inherit_gem: 5 | voxpupuli-rubocop: rubocop.yml 6 | 7 | Layout/LineLength: 8 | Exclude: 9 | - acceptance/**/*.rb 10 | - spec/**/*.rb 11 | 12 | Naming/AccessorMethodName: 13 | Enabled: false 14 | 15 | Naming/FileName: 16 | Exclude: 17 | - acceptance/**/acceptance-options.rb 18 | 19 | Naming/MethodParameterName: 20 | MinNameLength: 1 21 | 22 | Naming/PredicateName: 23 | Enabled: false 24 | 25 | RSpec/DescribeClass: 26 | Exclude: 27 | - acceptance/fixtures/module/spec/**/*.rb 28 | 29 | RSpec/MessageSpies: 30 | Enabled: false 31 | 32 | RSpec/StubbedMock: 33 | Enabled: false 34 | 35 | RSpec/MultipleExpectations: 36 | Enabled: false 37 | 38 | RSpec/IndexedLet: 39 | Enabled: false 40 | 41 | RSpec/NestedGroups: 42 | Max: 4 43 | 44 | Style: 45 | Enabled: false 46 | 47 | Naming/VariableNumber: 48 | AllowedPatterns: ['x86_64'] 49 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.configure do 2 | add_filter 'spec/' 3 | add_filter 'vendor/' 4 | add_filter do |file| 5 | file.lines_of_code < 10 6 | end 7 | add_group 'Answers', '/answers/' 8 | add_group 'DSL', '/dsl/' 9 | add_group 'Host', '/host/' 10 | add_group 'Hypervisors', '/hypervisor/' 11 | add_group 'Options', '/options/' 12 | add_group 'Shared', '/shared/' 13 | end 14 | 15 | SimpleCov.start if ENV['BEAKER_COVERAGE'] 16 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voxpupuli/beaker/65662adf24ca3f9f045eba264d1db4d5dd8248a9/CODEOWNERS -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 2 | 3 | gemspec 4 | gem 'stringio' 5 | # This section of the gemspec is for Puppet CI; it will pull in 6 | # a supported beaker library for testing to overwrite the gemspec if 7 | # a corresponding ENV var is found. Currently, the only supported lib 8 | # is beaker-pe, which can be injected into the dependencies when the 9 | # following ENV vars are defined: BEAKER_PE_PR_AUTHOR, 10 | # BEAKER_PE_PR_COMMIT, BEAKER_PE_PR_REPO_URL. These correspond to the 11 | # ghprb variables ghprbPullAuthorLogin, ghprbActualCommit, 12 | # and ghprbAuthorRepoGitUrl respectively. In the "future", we should 13 | # make this a standard format so we can pull in more than predefined 14 | # variables. 15 | 16 | if ENV['BEAKER_PE_PR_REPO_URL'] 17 | lib = ENV['BEAKER_PE_PR_REPO_URL'].match(/\/([^\/]+)\.git$/)[1] 18 | author = ENV.fetch('BEAKER_PE_PR_AUTHOR', nil) 19 | ref = ENV.fetch('BEAKER_PE_PR_COMMIT', nil) 20 | gem lib, :git => "git@github.com:#{author}/#{lib}.git", :branch => ref 21 | end 22 | 23 | if ENV['BEAKER_HYPERVISOR'] 24 | # vagrant_libvirt -> vagrant 25 | gem "beaker-#{ENV['BEAKER_HYPERVISOR'].split('_').first}" 26 | end 27 | 28 | group :release do 29 | gem 'faraday-retry', require: false 30 | gem 'github_changelog_generator', require: false 31 | end 32 | 33 | gem 'rdoc' if RUBY_VERSION >= '3.1' 34 | -------------------------------------------------------------------------------- /acceptance/config/acceptance-options.rb: -------------------------------------------------------------------------------- 1 | { 2 | :load_path => File.join('acceptance', 'lib'), 3 | :ssh => { 4 | :keys => ["id_rsa_acceptance", "#{ENV.fetch('HOME', nil)}/.ssh/id_rsa-acceptance"], 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /acceptance/config/base/acceptance-options.rb: -------------------------------------------------------------------------------- 1 | { 2 | :tests => 'acceptance/tests/base', 3 | }.merge(eval File.read('acceptance/config/acceptance-options.rb')) 4 | -------------------------------------------------------------------------------- /acceptance/config/hypervisor/acceptance-options.rb: -------------------------------------------------------------------------------- 1 | { 2 | :tests => 'acceptance/tests/hypervisor', 3 | }.merge(eval File.read('acceptance/config/acceptance-options.rb')) 4 | -------------------------------------------------------------------------------- /acceptance/config/subcommands/acceptance-options.rb: -------------------------------------------------------------------------------- 1 | { 2 | :pre_suite => 'acceptance/pre_suite/subcommands/', 3 | :tests => 'acceptance/tests/subcommands/', 4 | }.merge(eval File.read('acceptance/config/acceptance-options.rb')) 5 | -------------------------------------------------------------------------------- /acceptance/fixtures/README.md: -------------------------------------------------------------------------------- 1 | # Beaker Acceptance Tests: fixtures 2 | 3 | Any files needed to execute Beaker acceptance tests. 4 | -------------------------------------------------------------------------------- /acceptance/fixtures/files/failing_shell_script.txt: -------------------------------------------------------------------------------- 1 | 2 | # this shell script always fails 3 | exit 1 4 | -------------------------------------------------------------------------------- /acceptance/fixtures/files/retry_script.txt: -------------------------------------------------------------------------------- 1 | 2 | # This script which will fail the first `RETRY_LIMIT` times when run and then 3 | # will exit successfully on later runs. It will store counter state in a file 4 | # in the provided `BASEDIR` directory. 5 | set -x 6 | 7 | BASEDIR=${1} 8 | RETRY_LIMIT=${2} 9 | 10 | current=`cat ${BASEDIR}/value.txt || echo '0'` 11 | current=$((current+1)) 12 | echo "${current}" > ${BASEDIR}/value.txt 13 | if [ "$current" -gt "${RETRY_LIMIT}" ]; then exit 0; fi 14 | exit 1 15 | -------------------------------------------------------------------------------- /acceptance/fixtures/files/shell_script_with_output.txt: -------------------------------------------------------------------------------- 1 | 2 | # this shell script always succeeds, and produces output 3 | echo "output" 4 | -------------------------------------------------------------------------------- /acceptance/fixtures/files/simple_text_file.txt: -------------------------------------------------------------------------------- 1 | This is a simple text file. 2 | 3 | It has three lines. 4 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3'] 4 | gem 'facter', '>= 1.7.0' 5 | gem 'puppet', puppetversion 6 | gem 'puppetlabs_spec_helper', '>= 0.1.0' 7 | gem 'puppet-lint', '>= 0.3.2' 8 | 9 | group :system_tests do 10 | gem 'beaker-rspec', :require => false 11 | gem 'serverspec', :require => false 12 | end 13 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'puppetlabs_spec_helper/rake_tasks' 3 | require 'puppet-lint/tasks/puppet-lint' 4 | PuppetLint.configuration.send(:disable_80chars) 5 | PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] 6 | 7 | desc "Validate manifests, templates, and ruby files" 8 | task :validate do 9 | Dir['manifests/**/*.pp'].each do |manifest| 10 | sh "puppet parser validate --noop #{manifest}" 11 | end 12 | Dir['spec/**/*.rb', 'lib/**/*.rb'].each do |ruby_file| 13 | sh "ruby -c #{ruby_file}" unless ruby_file.include?('spec/fixtures') 14 | end 15 | Dir['templates/**/*.erb'].each do |template| 16 | sh "erb -P -x -T '-' #{template} | ruby -c" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/lib/empty.txt: -------------------------------------------------------------------------------- 1 | this is where module code would live 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # == Class: demo 2 | # 3 | # Full description of class demo here. 4 | # 5 | # === Parameters 6 | # 7 | # Document parameters here. 8 | # 9 | # [*sample_parameter*] 10 | # Explanation of what this parameter affects and what it defaults to. 11 | # e.g. "Specify one or more upstream ntp servers as an array." 12 | # 13 | # === Variables 14 | # 15 | # Here you should define a list of variables that this module would require. 16 | # 17 | # [*sample_variable*] 18 | # Explanation of how this variable affects the funtion of this class and if 19 | # it has a default. e.g. "The parameter enc_ntp_servers must be set by the 20 | # External Node Classifier as a comma separated list of hostnames." (Note, 21 | # global variables should be avoided in favor of class parameters as 22 | # of Puppet 2.6.) 23 | # 24 | # === Examples 25 | # 26 | # class { 'demo': 27 | # servers => [ 'pool.ntp.org', 'ntp.local.company.com' ], 28 | # } 29 | # 30 | # === Authors 31 | # 32 | # Author Name 33 | # 34 | # === Copyright 35 | # 36 | # Copyright 2015 Your name here, unless otherwise noted. 37 | # 38 | class demo { 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-demo", 3 | "version": "0.0.1", 4 | "author": "alice.nodelman", 5 | "summary": "For use in testing beaker.", 6 | "license": "Apache 2.0", 7 | "source": "no_source", 8 | "project_page": "no_page", 9 | "issues_url": "no_url", 10 | "dependencies": [ 11 | {"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"} 12 | ] 13 | } 14 | 15 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/demo_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper_acceptance' 2 | 3 | describe "my tests" do 4 | # an example using the beaker DSL 5 | # use http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL 6 | it "says hello!" do 7 | result = shell('echo hello') 8 | expect(result.stdout).to match(/hello/) 9 | end 10 | 11 | # an example using Serverspec 12 | # use http://serverspec.org/resource_types.html 13 | describe package('puppet') do 14 | it { is_expected.to be_installed } 15 | end 16 | 17 | it "can create and confirm a file" do 18 | shell('rm -f demo.txt') 19 | create_remote_file(default, 'demo.txt', 'foo\nfoo\nfoo\n') 20 | shell('grep foo demo.txt') 21 | shell('grep bar demo.txt', :acceptable_exit_codes => [1]) 22 | end 23 | 24 | it "is able to apply manifests" do 25 | manifest1 = "user {'foo': 26 | ensure => present,}" 27 | manifest2 = "user {'foo': 28 | ensure => absent,}" 29 | manifest3 = "user {'root': 30 | ensure => present,}" 31 | apply_manifest(manifest1, :expect_changes => true) 32 | apply_manifest(manifest2, :expect_changes => true) 33 | apply_manifest(manifest3) 34 | end 35 | 36 | describe service('sshd') do 37 | it { is_expected.to be_running } 38 | end 39 | 40 | describe user('root') do 41 | it { is_expected.to exist } 42 | end 43 | 44 | describe user('foo') do 45 | it { is_expected.not_to exist } 46 | end 47 | 48 | context "can use both serverspec and Beaker DSL" do 49 | it "can create a file" do 50 | shell('rm -f /tmp/demo.txt') 51 | manifest = "file {'demofile': 52 | path => '/tmp/demo.txt', 53 | ensure => present, 54 | mode => 0640, 55 | content => \"this is my file.\", 56 | }" 57 | apply_manifest(manifest, :expect_changes => true) 58 | end 59 | 60 | describe file('/tmp/demo.txt') do 61 | it { is_expected.to be_file } 62 | it { is_expected.to contain 'this is my file.' } 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/centos-59-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-59-x64: 3 | roles: 4 | - master 5 | platform: el-5-x86_64 6 | box : centos-59-x64-vbox4210-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: git 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/centos-64-x64-pe.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-64-x64: 3 | roles: 4 | - master 5 | - database 6 | - dashboard 7 | platform: el-6-x86_64 8 | box : centos-64-x64-vbox4210-nocm 9 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box 10 | hypervisor : vagrant 11 | CONFIG: 12 | type: pe 13 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/centos-64-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-64-x64: 3 | roles: 4 | - master 5 | platform: el-6-x86_64 6 | box : centos-64-x64-vbox4210-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: foss 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/centos-65-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-65-x64: 3 | roles: 4 | - master 5 | platform: el-6-x86_64 6 | box : centos-65-x64-vbox436-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: foss 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/default.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-64-x64: 3 | roles: 4 | - master 5 | platform: el-6-x86_64 6 | box : centos-64-x64-vbox4210-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: foss 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/internal-vpool.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | centos-7-x86_64-master: 3 | roles: 4 | - agent 5 | - dashboard 6 | - database 7 | - master 8 | hypervisor: vcloud 9 | platform: centos-7-x86_64 10 | template: Delivery/Quality Assurance/Templates/vCloud/centos-7-x86_64 11 | CONFIG: 12 | pooling_api: http://vcloud.delivery.puppetlabs.net 13 | datastore: instance0 14 | folder: Delivery/Quality Assurance/Staging/Dynamic 15 | resourcepool: delivery/Quality Assurance/Staging/Dynamic 16 | nfs_server: none 17 | consoleport: 443 18 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/sles-11-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | sles-11-x64.local: 3 | roles: 4 | - master 5 | platform: sles-11-x64 6 | box : sles-11sp1-x64-vbox4210-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/sles-11sp1-x64-vbox4210-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: foss 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | ubuntu-server-10044-x64: 3 | roles: 4 | - master 5 | platform: ubuntu-10.04-amd64 6 | box : ubuntu-server-10044-x64-vbox4210-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: foss 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | ubuntu-server-12042-x64: 3 | roles: 4 | - master 5 | platform: ubuntu-12.04-amd64 6 | box : ubuntu-server-12042-x64-vbox4210-nocm 7 | box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box 8 | hypervisor : vagrant 9 | CONFIG: 10 | type: foss 11 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | ubuntu-server-1404-x64: 3 | roles: 4 | - master 5 | platform: ubuntu-14.04-amd64 6 | box : puppetlabs/ubuntu-14.04-64-nocm 7 | box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm 8 | hypervisor : vagrant 9 | CONFIG: 10 | log_level : debug 11 | type: git 12 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-14042-x64.yml: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | ubuntu1404: 3 | roles: 4 | - agent 5 | platform: ubuntu-14.04-amd64 6 | template: ubuntu-1404-x86_64 7 | hypervisor: vcloud 8 | CONFIG: 9 | type: foss 10 | keyfile: ~/.ssh/id_rsa-acceptance 11 | nfs_server: none 12 | consoleport: 443 13 | datastore: instance0 14 | folder: Delivery/Quality Assurance/Enterprise/Dynamic 15 | resourcepool: delivery/Quality Assurance/Enterprise/Dynamic 16 | pooling_api: http://vcloud.delivery.puppetlabs.net/ 17 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/classes/init_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe 'demo' do 3 | context 'with defaults for all parameters' do 4 | it { is_expected.to contain_class('demo') } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'puppetlabs_spec_helper/module_spec_helper' 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | require 'beaker-rspec' 2 | 3 | unless ENV['RS_PROVISION'] == 'no' or ENV['BEAKER_provision'] == 'no' 4 | # This will install the latest available package on el and deb based 5 | # systems fail on windows and osx, and install via gem on other *nixes 6 | foss_opts = { :default_action => 'gem_install' } 7 | 8 | if default.is_pe?; then 9 | install_pe; 10 | else 11 | install_puppet(foss_opts); 12 | end 13 | 14 | hosts.each do |host| 15 | on host, "/bin/echo '' > #{host.puppet('hiera_config')}" unless host.is_pe? 16 | on host, "mkdir -p #{host['distmoduledir']}" 17 | end 18 | end 19 | 20 | RSpec.configure do |c| 21 | # Project root 22 | proj_root = File.expand_path(File.join(__dir__, '..')) 23 | 24 | # Readable test descriptions 25 | c.formatter = :documentation 26 | 27 | # Configure all nodes in nodeset 28 | c.before :suite do 29 | hosts.each do |host| 30 | on host, "mkdir -p #{host['distmoduledir']}/demo" 31 | %w(lib manifests metadata.json).each do |file| 32 | scp_to host, "#{proj_root}/#{file}", "#{host['distmoduledir']}/ntp" 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/tests/init.pp: -------------------------------------------------------------------------------- 1 | # The baseline for module testing used by Puppet Labs is that each manifest 2 | # should have a corresponding test manifest that declares that class or defined 3 | # type. 4 | # 5 | # Tests are then run by using puppet apply --noop (to check for compilation 6 | # errors and view a log of events) or by fully applying the test in a virtual 7 | # environment (to compare the resulting system state to the desired state). 8 | # 9 | # Learn more about module testing here: 10 | # http://docs.puppetlabs.com/guides/tests_smoke.html 11 | # 12 | include demo 13 | -------------------------------------------------------------------------------- /acceptance/fixtures/module/vendor/bundle/ruby/gems.txt: -------------------------------------------------------------------------------- 1 | test gems would live in this directory 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-jessie.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/jessie jessie PC1 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-lucid.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/lucid lucid PC1 -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-precise.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/precise precise PC1 -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-squeeze.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/squeeze squeeze PC1 -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-trusty.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/trusty trusty PC1 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-vivid.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/vivid vivid PC1 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-wheezy.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/wheezy wheezy PC1 -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-wily.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/wily wily PC1 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/deb/pl-puppetserver-latest-xenial.list: -------------------------------------------------------------------------------- 1 | deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/xenial xenial PC1 2 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-6-i386.repo: -------------------------------------------------------------------------------- 1 | [pl-puppetserver-latest] 2 | name=PL Repo for puppetserver at commit latest 3 | baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/6/PC1/i386/ 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=http://nightlies.puppetlabs.com/07BB6C57 7 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-6-x86_64.repo: -------------------------------------------------------------------------------- 1 | [pl-puppetserver-latest] 2 | name=PL Repo for puppetserver at commit latest 3 | baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/6/PC1/x86_64/ 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=http://nightlies.puppetlabs.com/07BB6C57 7 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-7-i386.repo: -------------------------------------------------------------------------------- 1 | [pl-puppetserver-latest] 2 | name=PL Repo for puppetserver at commit latest 3 | baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/7/PC1/i386/ 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=http://nightlies.puppetlabs.com/07BB6C57 7 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-7-x86_64.repo: -------------------------------------------------------------------------------- 1 | [pl-puppetserver-latest] 2 | name=PL Repo for puppetserver at commit latest 3 | baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/7/PC1/x86_64/ 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=http://nightlies.puppetlabs.com/07BB6C57 7 | -------------------------------------------------------------------------------- /acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-sles-12-x86_64.repo: -------------------------------------------------------------------------------- 1 | [pl-puppetserver-latest] 2 | name=PL Repo for puppetserver at commit latest 3 | baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/sles/12/PC1/x86_64/ 4 | enabled=1 5 | gpgcheck=1 6 | gpgkey=http://nightlies.puppetlabs.com/07BB6C57 -------------------------------------------------------------------------------- /acceptance/pre_suite/README.md: -------------------------------------------------------------------------------- 1 | # Beaker Acceptance Tests: pre-suite 2 | 3 | Collection of pre-suites for SUT configuration before test execution. 4 | 5 | Options: 6 | * install PE 7 | * install FOSS Puppet 8 | * install beaker itself on a SUT 9 | -------------------------------------------------------------------------------- /acceptance/pre_suite/pe/install.rb: -------------------------------------------------------------------------------- 1 | # require beaker-pe to load in the additional DSL methods 2 | require 'beaker-pe' 3 | install_pe 4 | -------------------------------------------------------------------------------- /acceptance/pre_suite/subcommands/05_install_ruby.rb: -------------------------------------------------------------------------------- 1 | ruby_version, ruby_source = ENV.fetch('RUBY_VER', nil), "job parameter" 2 | unless ruby_version 3 | ruby_version = "2.4.1" 4 | ruby_source = "default" 5 | end 6 | test_name "Install and configure Ruby #{ruby_version} (from #{ruby_source}) on the SUT" do 7 | step 'Ensure that the default system is an el-based system' do 8 | # The pre-suite currently only supports el systems, and we should 9 | # fail early if the default platform is not a supported platform 10 | assert(default.platform.variant == 'el', 11 | "Expected the platform variant to be 'el', not #{default.platform.variant}") 12 | end 13 | 14 | step 'clean out current ruby and its dependencies' do 15 | on default, 'yum remove ruby ruby-devel -y' 16 | end 17 | 18 | # These steps install git, openssl, and wget 19 | step 'install development dependencies' do 20 | on default, 'yum groupinstall "Development Tools" -y' 21 | on default, 'yum install openssl-devel -y' 22 | on default, 'yum install wget -y' 23 | end 24 | 25 | step "download and install ruby #{ruby_version}" do 26 | on default, "wget http://cache.ruby-lang.org/pub/ruby/#{ruby_version[0..2]}/ruby-#{ruby_version}.tar.gz" 27 | on default, "tar xvfz ruby-#{ruby_version}.tar.gz" 28 | on default, "cd ruby-#{ruby_version};./configure" 29 | on default, "cd ruby-#{ruby_version};make" 30 | on default, "cd ruby-#{ruby_version};make install" 31 | end 32 | 33 | step 'update gem on the SUT and install bundler' do 34 | on default, 'gem update --system;gem install --force bundler' 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /acceptance/pre_suite/subcommands/08_install_beaker.rb: -------------------------------------------------------------------------------- 1 | test_name 'Install beaker and checkout branch if necessary' do 2 | step 'Download the beaker git repo' do 3 | on default, 'git clone https://github.com/puppetlabs/beaker.git /opt/beaker/' 4 | end 5 | 6 | step 'Detect if checking out branch for testing and checkout' do 7 | if ENV['BEAKER_PULL_ID'] 8 | logger.notify "Pull Request detected, checking out PR branch" 9 | on(default, 'cd /opt/beaker/;git -c core.askpass=true fetch --tags --progress https://github.com/puppetlabs/beaker.git +refs/pull/*:refs/remotes/origin/pr/*') 10 | on(default, "cd /opt/beaker/;git merge origin/pr/#{ENV['BEAKER_PULL_ID']}/head --no-edit") 11 | else 12 | logger.notify 'No PR branch detected, building from master' 13 | end 14 | end 15 | 16 | step 'Build the gem and install it on the local system' do 17 | build_output = on(default, 'cd /opt/beaker/;gem build beaker.gemspec').stdout 18 | version = build_output.match(/^ File: (.+)$/)[1] 19 | on(default, "cd /opt/beaker/;gem install #{version} --no-document; gem install beaker-vmpooler") 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /acceptance/scripts/all_but_host_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | top_level=$(git rev-parse --show-toplevel) 4 | acceptance_test_base="$top_level/acceptance/tests/base" 5 | 6 | find $acceptance_test_base -type f -name '*.rb' | 7 | grep -v host_test.rb | 8 | awk 'BEGIN { 9 | comma_index = 1 10 | } { 11 | if (comma_index == 1) { 12 | comma_string = $0 13 | } else { 14 | comma_string = comma_string "," $0 15 | } 16 | comma_index = comma_index + 1 17 | } END { 18 | print comma_string 19 | }' 20 | -------------------------------------------------------------------------------- /acceptance/tests/base/README.md: -------------------------------------------------------------------------------- 1 | # Beaker Acceptance Tests: base 2 | 3 | Tests that do not depend upon Puppet being installed on the SUTs. 4 | 5 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/configuration_test.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path(File.join(__dir__, '..', '..', '..', '..', 'lib')) 2 | 3 | require 'helpers/test_helper' 4 | 5 | test_name "dsl::helpers::host_helpers test configuration validation" do 6 | step "Validate hosts configuration" do 7 | assert (hosts.size > 1), 8 | "dsl::helpers::host_helpers acceptance tests require at least two hosts" 9 | 10 | agents = select_hosts(:roles => "agent") 11 | 12 | assert (agents.size > 1), 13 | "dsl::helpers::host_helpers acceptance tests require at least two hosts with the :agent role" 14 | 15 | assert default, 16 | "dsl::helpers::host_helpers acceptance tests require a default host" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/add_system32_hosts_entry_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #add_system32_hosts_entry" do 4 | confine_block :to, :platform => /windows/ do 5 | step "#add_system32_hosts_entry fails when run on a non-powershell platform" do 6 | # NOTE: would expect this to be better documented. 7 | if default.is_powershell? 8 | logger.info "Skipping failure test on powershell platforms..." 9 | else 10 | assert_raises RuntimeError do 11 | add_system32_hosts_entry default, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } 12 | end 13 | end 14 | end 15 | 16 | step "#add_system32_hosts_entry, when run on a powershell platform, adds a host entry to system32 etc\\hosts" do 17 | if default.is_powershell? 18 | add_system32_hosts_entry default, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } 19 | 20 | # TODO: how do we assert, via powershell, that the entry was added? 21 | # NOTE: see: https://github.com/puppetlabs/beaker/commit/685628f4babebe9cb4663418da6a8ff528dd32da#commitcomment-12957573 22 | 23 | else 24 | logger.info "Skipping test on non-powershell platforms..." 25 | end 26 | end 27 | 28 | step "#add_system32_hosts_entry CURRENTLY fails with a TypeError when given a hosts array" do 29 | # NOTE: would expect this to fail with Beaker::Host::CommandFailure 30 | assert_raises NoMethodError do 31 | add_system32_hosts_entry hosts, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } 32 | end 33 | end 34 | end 35 | 36 | confine_block :except, :platform => /windows/ do 37 | step "#add_system32_hosts_entry CURRENTLY fails with RuntimeError when run on a non-windows platform" do 38 | # NOTE: would expect this to behave the same way it does on a windows 39 | # non-powershell platform (raises Beaker::Host::CommandFailure), or 40 | # as requested in the original PR: 41 | # https://github.com/puppetlabs/beaker/pull/420/files#r17990622 42 | assert_raises RuntimeError do 43 | add_system32_hosts_entry default, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/archive_file_from_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #archive_file_from" do 4 | step "archiveroot parameter defaults to `archive/sut-files`" do 5 | # Create a remote file to archive 6 | filepath = default.tmpfile('archive-file-test') 7 | create_remote_file(default, filepath, 'contents ignored') 8 | 9 | # Prepare cleanup so we don't pollute the local filesystem 10 | teardown do 11 | FileUtils.rm_rf('archive') if Dir.exist?('archive') 12 | end 13 | 14 | # Test that the archiveroot default directory is created 15 | assert_equal(false, Dir.exist?('archive')) 16 | assert_equal(false, Dir.exist?('archive/sut-files')) 17 | archive_file_from(default, filepath) 18 | 19 | assert_equal(true, Dir.exist?('archive/sut-files')) 20 | end 21 | 22 | step "fails archive_file_from when from_path is non-existant" do 23 | # beaker-docker can't deal with closing the connection 24 | confine :except, :hypervisor => 'docker' 25 | 26 | filepath = "foo-filepath-should-not-exist" 27 | assert_raises IOError do 28 | archive_file_from(default, filepath) 29 | end 30 | end 31 | 32 | step "archive is copied to local // directory" do 33 | # Create a remote file to archive 34 | filepath = default.tmpfile('archive-file-test') 35 | create_remote_file(default, filepath, 'number of the beast') 36 | 37 | assert_equal(false, Dir.exist?(filepath)) 38 | 39 | # Test that the file is copied locally to // 40 | Dir.mktmpdir do |tmpdir| 41 | tar_path = File.join(tmpdir, default, filepath + '.tgz') 42 | archive_file_from(default, filepath, {}, tmpdir, tar_path) 43 | 44 | assert_path_exists(tar_path) 45 | expected_path = File.join(tmpdir, default) 46 | 47 | tgz = Zlib::GzipReader.new(File.open(tar_path, 'rb')) 48 | Minitar.unpack(tgz, expected_path) 49 | 50 | assert_equal('number of the beast', File.read(expected_path + '/' + filepath).strip) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/backup_the_file_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #backup_the_file" do 4 | step "#backup_the_file CURRENTLY will return nil if the file does not exist in the source directory" do 5 | # NOTE: would expect this to fail with Beaker::Host::CommandFailure 6 | remote_source = default.tmpdir 7 | remote_destination = default.tmpdir 8 | result = backup_the_file default, remote_source, remote_destination 9 | 10 | assert_nil result 11 | end 12 | 13 | step "#backup_the_file will fail if the destination directory does not exist" do 14 | remote_source = default.tmpdir 15 | create_remote_file_from_fixture("simple_text_file", default, remote_source, "puppet.conf") 16 | 17 | assert_raises Beaker::Host::CommandFailure do 18 | backup_the_file default, remote_source, "/non/existent/" 19 | end 20 | end 21 | 22 | step "#backup_the_file copies `puppet.conf` from the source to the destination directory" do 23 | remote_source = default.tmpdir 24 | _remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_source, "puppet.conf") 25 | 26 | remote_destination = default.tmpdir 27 | remote_destination_filename = File.join(remote_destination, "puppet.conf.bak") 28 | 29 | result = backup_the_file default, remote_source, remote_destination 30 | 31 | assert_equal remote_destination_filename, result 32 | remote_contents = on(default, "cat #{remote_destination_filename}").stdout 33 | 34 | assert_equal contents, remote_contents 35 | end 36 | 37 | step "#backup_the_file copies a named file from the source to the destination directory" do 38 | remote_source = default.tmpdir 39 | _remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_source, "testfile.txt") 40 | 41 | remote_destination = default.tmpdir 42 | remote_destination_filename = File.join(remote_destination, "testfile.txt.bak") 43 | 44 | result = backup_the_file default, remote_source, remote_destination, "testfile.txt" 45 | 46 | assert_equal remote_destination_filename, result 47 | remote_contents = on(default, "cat #{remote_destination_filename}").stdout 48 | 49 | assert_equal contents, remote_contents 50 | end 51 | 52 | step "#backup_the_file CURRENTLY will fail if given a hosts array" do 53 | remote_source = default.tmpdir 54 | create_remote_file_from_fixture("simple_text_file", default, remote_source, "testfile.txt") 55 | remote_destination = default.tmpdir 56 | 57 | assert_raises NoMethodError do 58 | backup_the_file hosts, remote_source, remote_destination 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/curl_on_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | # construct an appropriate local file URL for curl testing 4 | def host_local_url(host, path) 5 | if host.is_cygwin? 6 | path_result = on(host, "cygpath #{path}") 7 | path = path_result.raw_output.chomp 8 | end 9 | "file://#{path}" 10 | end 11 | 12 | test_name "dsl::helpers::host_helpers #curl_on" do 13 | step "#curl_on fails if the URL in question cannot be reached" do 14 | assert Beaker::Host::CommandFailure do 15 | curl_on default, "file:///non/existent.html" 16 | end 17 | end 18 | 19 | step "#curl_on can retrieve the contents of a URL, using standard curl options" do 20 | remote_tmpdir = default.tmpdir 21 | remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_tmpdir, "testfile.txt") 22 | remote_targetfilename = File.join remote_tmpdir, "outfile.txt" 23 | 24 | result = curl_on default, "-o #{remote_targetfilename} #{host_local_url default, remote_filename}" 25 | 26 | assert_equal 0, result.exit_code 27 | remote_contents = on(default, "cat #{remote_targetfilename}").stdout 28 | 29 | assert_equal contents, remote_contents 30 | end 31 | 32 | step "#curl_on can retrieve the contents of a URL, when given a hosts array" do 33 | remote_tmpdir = default.tmpdir 34 | on hosts, "mkdir -p #{remote_tmpdir}" 35 | 36 | remote_filename = contents = nil 37 | hosts.each do |host| 38 | remote_filename, contents = create_remote_file_from_fixture("simple_text_file", host, remote_tmpdir, "testfile.txt") 39 | end 40 | remote_targetfilename = File.join remote_tmpdir, "outfile.txt" 41 | 42 | curl_on hosts, "-o #{remote_targetfilename} #{host_local_url default, remote_filename}" 43 | 44 | hosts.each do |host| 45 | remote_contents = on(host, "cat #{remote_targetfilename}").stdout 46 | 47 | assert_equal contents, remote_contents 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/curl_with_retries_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #curl_with_retries" do 4 | step "#curl_with_retries CURRENTLY fails with a RuntimeError if retries are exhausted without fetching the specified URL" do 5 | # NOTE: would expect that this would raise Beaker::Host::CommandFailure 6 | 7 | assert_raises RuntimeError do 8 | curl_with_retries \ 9 | "description", 10 | default, 11 | "file:///non/existent.html", 12 | desired_exit_codes: [0], 13 | max_retries: 2, 14 | retry_interval: 0.01 15 | end 16 | end 17 | 18 | step "#curl_with_retries retrieves the contents of a URL after retrying" do 19 | # TODO: testing curl_with_retries relies on having a portable means of 20 | # making an unavailable URL available after a period of time. 21 | end 22 | 23 | step "#curl_with_retries can retrieve the contents of a URL after retrying, when given a hosts array" do 24 | # TODO: testing curl_with_retries relies on having a portable means of 25 | # making an unavailable URL available after a period of time. 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/echo_on_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #echo_on" do 4 | step "#echo_on echoes the supplied string on the remote host" do 5 | output = echo_on(default, "contents") 6 | 7 | assert_equal("contents", output) 8 | end 9 | 10 | step "#echo_on echoes the supplied string on all hosts when given a hosts array" do 11 | results = echo_on(hosts, "contents") 12 | 13 | assert_equal ["contents"] * hosts.size, results 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/install_package_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #install_package" do 4 | step "#install_package is just a wrapper for Host#install_package, see acceptance/tests/base/host/packages.rb" 5 | end 6 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/retry_on_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #retry_on" do 4 | step "#retry_on CURRENTLY fails with a RuntimeError if command does not pass after all retries" do 5 | # NOTE: would have expected this to fail with Beaker::Hosts::CommandFailure 6 | 7 | remote_tmpdir = default.tmpdir 8 | remote_script_file = File.join(remote_tmpdir, "test.sh") 9 | create_remote_file_from_fixture("retry_script", default, remote_tmpdir, "test.sh") 10 | 11 | assert_raises RuntimeError do 12 | retry_on default, "bash #{remote_script_file} #{remote_tmpdir} 10", { :max_retries => 2, :retry_interval => 0.1 } 13 | end 14 | end 15 | 16 | step "#retry_on succeeds if command passes before retries are exhausted" do 17 | remote_tmpdir = default.tmpdir 18 | remote_script_file = File.join(remote_tmpdir, "test.sh") 19 | create_remote_file_from_fixture("retry_script", default, remote_tmpdir, "test.sh") 20 | 21 | result = retry_on default, "bash #{remote_script_file} #{remote_tmpdir} 2", { :max_retries => 4, :retry_interval => 0.1 } 22 | 23 | assert_equal 0, result.exit_code 24 | assert_equal "", result.stdout 25 | end 26 | 27 | step "#retry_on CURRENTLY fails when provided a host array" do 28 | # NOTE: would expect this to work across hosts, or be better documented and 29 | # to raise Beaker::Host::CommandFailure 30 | 31 | remote_tmpdir = default.tmpdir 32 | remote_script_file = File.join(remote_tmpdir, "test.sh") 33 | 34 | hosts.each do |host| 35 | on host, "mkdir -p #{remote_tmpdir}" 36 | create_remote_file_from_fixture("retry_script", host, remote_tmpdir, "test.sh") 37 | end 38 | 39 | assert_raises NoMethodError do 40 | retry_on hosts, "bash #{remote_script_file} #{remote_tmpdir} 2", { :max_retries => 4, :retry_interval => 0.1 } 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/run_script_on_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #run_script_on" do 4 | step "#run_script_on fails when the local script cannot be found" do 5 | assert_raises IOError do 6 | run_script_on default, "/non/existent/testfile.sh" 7 | end 8 | end 9 | 10 | step "#run_script_on fails when there is an error running the remote script" do 11 | Dir.mktmpdir do |local_dir| 12 | local_filename, _contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") 13 | 14 | assert_raises Beaker::Host::CommandFailure do 15 | run_script_on default, local_filename 16 | end 17 | end 18 | end 19 | 20 | step "#run_script_on passes along options when running the remote command" do 21 | Dir.mktmpdir do |local_dir| 22 | local_filename, _contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") 23 | 24 | result = run_script_on default, local_filename, { :accept_all_exit_codes => true } 25 | 26 | assert_equal 1, result.exit_code 27 | end 28 | end 29 | 30 | step "#run_script_on runs the script on the remote host" do 31 | Dir.mktmpdir do |local_dir| 32 | local_filename, _contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") 33 | 34 | results = run_script_on default, local_filename 35 | 36 | assert_equal 0, results.exit_code 37 | assert_equal "output\n", results.stdout 38 | end 39 | end 40 | 41 | step "#run_script_on allows assertions in an optional block" do 42 | Dir.mktmpdir do |local_dir| 43 | local_filename, _contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") 44 | 45 | run_script_on default, local_filename do |result| 46 | assert_equal 0, result.exit_code 47 | assert_equal "output\n", result.stdout 48 | end 49 | end 50 | end 51 | 52 | step "#run_script_on runs the script on all remote hosts when a host array is provided" do 53 | Dir.mktmpdir do |local_dir| 54 | local_filename, _contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") 55 | 56 | results = run_script_on hosts, local_filename 57 | 58 | assert_equal hosts.size, results.size 59 | results.each do |result| 60 | assert_equal 0, result.exit_code 61 | assert_equal "output\n", result.stdout 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/run_script_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #run_script" do 4 | step "#run_script fails when the local script cannot be found" do 5 | assert_raises IOError do 6 | run_script "/non/existent/testfile.sh" 7 | end 8 | end 9 | 10 | step "#run_script fails when there is an error running the remote script" do 11 | Dir.mktmpdir do |local_dir| 12 | local_filename, _contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") 13 | 14 | assert_raises Beaker::Host::CommandFailure do 15 | run_script local_filename 16 | end 17 | end 18 | end 19 | 20 | step "#run_script passes along options when running the remote command" do 21 | Dir.mktmpdir do |local_dir| 22 | local_filename, _contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") 23 | 24 | result = run_script local_filename, { :accept_all_exit_codes => true } 25 | 26 | assert_equal 1, result.exit_code 27 | end 28 | end 29 | 30 | step "#run_script runs the script on the remote host" do 31 | Dir.mktmpdir do |local_dir| 32 | local_filename, _contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") 33 | 34 | results = run_script local_filename 35 | 36 | assert_equal 0, results.exit_code 37 | assert_equal "output\n", results.stdout 38 | end 39 | end 40 | 41 | step "#run_script allows assertions in an optional block" do 42 | Dir.mktmpdir do |local_dir| 43 | local_filename, _contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") 44 | 45 | run_script local_filename do |result| 46 | assert_equal 0, result.exit_code 47 | assert_equal "output\n", result.stdout 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/scp_from_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #scp_from" do 4 | if test_scp_error_on_close? 5 | step "#scp_from fails if the local path cannot be found" do 6 | remote_tmpdir = default.tmpdir 7 | remote_filename, _contents = create_remote_file_from_fixture("simple_text_file", default, remote_tmpdir, "testfile.txt") 8 | 9 | assert_raises Beaker::Host::CommandFailure do 10 | scp_from default, remote_filename, "/non/existent/file.txt" 11 | end 12 | end 13 | 14 | step "#scp_from fails if the remote file cannot be found" do 15 | Dir.mktmpdir do |local_dir| 16 | assert_raises Beaker::Host::CommandFailure do 17 | scp_from default, "/non/existent/remote/file.txt", local_dir 18 | end 19 | end 20 | end 21 | end 22 | 23 | step "#scp_from creates the file on the local system" do 24 | Dir.mktmpdir do |local_dir| 25 | remote_tmpdir = default.tmpdir 26 | remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_tmpdir, "testfile.txt") 27 | 28 | scp_from default, remote_filename, local_dir 29 | 30 | local_filename = File.join(local_dir, "testfile.txt") 31 | 32 | assert_equal contents, File.read(local_filename) 33 | end 34 | end 35 | 36 | step "#scp_from CURRENTLY creates and repeatedly overwrites the file on the local system when given a hosts array" do 37 | # NOTE: expect this behavior to be well-documented, or for overwriting a 38 | # file repeatedly to generate an error 39 | 40 | Dir.mktmpdir do |local_dir| 41 | remote_tmpdir = default.tmpdir 42 | remote_filename = File.join(remote_tmpdir, "testfile.txt") 43 | on hosts, "mkdir -p #{remote_tmpdir}" 44 | on hosts, %{echo "${RANDOM}:${RANDOM}:${RANDOM}" > #{remote_filename}} 45 | 46 | scp_from hosts, remote_filename, local_dir 47 | remote_contents = on(hosts.last, "cat #{remote_filename}").stdout 48 | 49 | local_filename = File.join(local_dir, "testfile.txt") 50 | local_contents = File.read(local_filename) 51 | 52 | assert_equal remote_contents, local_contents 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/scp_to_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #scp_to" do 4 | step "#scp_to fails if the local file cannot be found" do 5 | remote_tmpdir = default.tmpdir 6 | assert_raises IOError do 7 | scp_to default, "/non/existent/file.txt", remote_tmpdir 8 | end 9 | end 10 | 11 | if test_scp_error_on_close? 12 | step "#scp_to fails if the remote path cannot be found" do 13 | Dir.mktmpdir do |local_dir| 14 | local_filename, _contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") 15 | 16 | # assert_raises Beaker::Host::CommandFailure do 17 | assert_raises RuntimeError do 18 | scp_to default, local_filename, "/non/existent/remote/file.txt" 19 | end 20 | end 21 | end 22 | end 23 | 24 | step "#scp_to creates the file on the remote system" do 25 | Dir.mktmpdir do |local_dir| 26 | local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") 27 | remote_tmpdir = default.tmpdir 28 | 29 | scp_to default, local_filename, remote_tmpdir 30 | 31 | remote_filename = File.join(remote_tmpdir, "testfile.txt") 32 | remote_contents = on(default, "cat #{remote_filename}").stdout 33 | 34 | assert_equal contents, remote_contents 35 | end 36 | end 37 | 38 | step "#scp_to creates the file on all remote systems when a host array is provided" do 39 | Dir.mktmpdir do |local_dir| 40 | local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") 41 | 42 | remote_tmpdir = default.tmpdir 43 | on hosts, "mkdir -p #{remote_tmpdir}" 44 | remote_filename = File.join(remote_tmpdir, "testfile.txt") 45 | 46 | scp_to hosts, local_filename, remote_tmpdir 47 | 48 | hosts.each do |host| 49 | remote_contents = on(host, "cat #{remote_filename}").stdout 50 | 51 | assert_equal contents, remote_contents 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/helpers/host_helpers/shell_test.rb: -------------------------------------------------------------------------------- 1 | require "helpers/test_helper" 2 | 3 | test_name "dsl::helpers::host_helpers #shell" do 4 | step "#shell raises an exception when remote command fails" do 5 | assert_raises(Beaker::Host::CommandFailure) do 6 | shell "/bin/nonexistent-command" 7 | end 8 | end 9 | 10 | step "#shell makes command output available via `.stdout` on success" do 11 | output = shell(%{echo "echo via on"}).stdout 12 | 13 | assert_equal "echo via on\n", output 14 | end 15 | 16 | step "#shell makes command error output available via `.stderr` on success" do 17 | output = shell("/bin/nonexistent-command", :acceptable_exit_codes => [0, 127]).stderr 18 | 19 | assert_match(/No such file/, output) 20 | end 21 | 22 | step "#shell makes exit status available via `.exit_code`" do 23 | status = shell(%{echo "echo via on"}).exit_code 24 | 25 | assert_equal 0, status 26 | end 27 | 28 | step "#shell with :acceptable_exit_codes will not fail for named exit codes" do 29 | result = shell "/bin/nonexistent-command", :acceptable_exit_codes => [0, 127] 30 | output = result.stderr 31 | 32 | assert_match(/No such file/, output) 33 | status = result.exit_code 34 | 35 | assert_equal 127, status 36 | end 37 | 38 | step "#shell with :acceptable_exit_codes will fail for other exit codes" do 39 | assert_raises(Beaker::Host::CommandFailure) do 40 | shell %{echo "echo via on"}, :acceptable_exit_codes => [127] 41 | end 42 | end 43 | 44 | step "#shell will pass :environment options to the remote host as ENV settings" do 45 | result = shell "env", { :environment => { 'FOO' => 'bar' } } 46 | output = result.stdout 47 | 48 | assert_match(/\bFOO=bar\b/, output) 49 | end 50 | 51 | step "#shell allows assertions to be used in the optional block" do 52 | shell %{echo "${RANDOM}:${RANDOM}"} do |result| 53 | assert_match(/\d+:\d+/, result.stdout) 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /acceptance/tests/base/dsl/platform_tag_confiner_test.rb: -------------------------------------------------------------------------------- 1 | test_name "DSL::Structure::PlatformTagConfiner" do 2 | pstc_method_name = "#platform_specific_tag_confines" 3 | step "#{pstc_method_name} doesn't change hosts if there are no tags" do 4 | previous_hosts = hosts.dup 5 | 6 | platform_specific_tag_confines 7 | 8 | assert_equal previous_hosts, hosts, "#{pstc_method_name} changed the hosts array" 9 | # cleanup 10 | options[:platform_tag_confines_object] = nil 11 | options[:platform_tag_confines] = nil 12 | @hosts = previous_hosts 13 | end 14 | 15 | step "#{pstc_method_name} can remove hosts from a test, or be skipped if empty" do 16 | assert_operator hosts.length, :>, 0, "#{pstc_method_name} did not have enough hosts to test" 17 | previous_hosts = hosts.dup 18 | 19 | options[:platform_tag_confines] = [ 20 | :platform => /#{default[:platform]}/, 21 | :tag_reason_hash => { 22 | 'tag1' => 'reason1', 23 | }, 24 | ] 25 | 26 | begin 27 | tag('tag1') 28 | rescue Beaker::DSL::Outcomes::SkipTest => e 29 | fail "#{pstc_method_name} raised unexpected SkipTest exception: #{e}" unless /^No\ suitable\ hosts\ found/.match?(e.message) 30 | # SkipTest is raised in the case when there are no hosts leftover for a test 31 | # after confining. It's a very common acceptance test case where all of the 32 | # hosts involved are of the same platform, and are thus all confined 33 | # away by the code being run here. In this case, the hosts object will not 34 | # be altered, but should be considered a pass, since the fact that SkipTest 35 | # is being raised confirms that a lower number of hosts are coming out of 36 | # the confine (0) than came in (>0, according to our pre-condition assertion) 37 | else 38 | assert_operator hosts.length, :<, previous_hosts.length, "#{pstc_method_name} did not change hosts array" 39 | end 40 | 41 | # cleanup 42 | options[:platform_tag_confines_object] = nil 43 | options[:platform_tag_confines] = nil 44 | @hosts = previous_hosts 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /acceptance/tests/base/host/group_test.rb: -------------------------------------------------------------------------------- 1 | test_name 'Group Test' do 2 | step "#group_get: has an Administrators group on Windows" do 3 | hosts.select { |h| h['platform'].include?('windows') }.each do |host| 4 | host.group_get('Administrators') do |result| 5 | refute_match(result.stdout, '1376', 'Output indicates Administrators not found') 6 | end 7 | end 8 | end 9 | 10 | step "#group_get: should not have CroMags group on Windows" do 11 | hosts.select { |h| h['platform'].include?('windows') }.each do |host| 12 | assert_raises Beaker::Host::CommandFailure do 13 | host.group_get('CroMags') { |result| } 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /acceptance/tests/base/host/packages.rb: -------------------------------------------------------------------------------- 1 | test_name 'confirm packages on hosts behave correctly' 2 | confine :except, :platform => %w(osx) 3 | 4 | step '#check_for_command : can determine where a command exists' 5 | hosts.each do |host| 6 | logger.debug "echo package should be installed on #{host}" 7 | assert(host.check_for_command('echo'), "'echo' should be a command") 8 | logger.debug("doesnotexist package should not be installed on #{host}") 9 | assert_equal(false, host.check_for_command('doesnotexist'), '"doesnotexist" should not be a command') 10 | end 11 | 12 | step '#check_for_package : can determine if a package is installed' 13 | hosts.each do |host| 14 | package = 'bash' 15 | 16 | logger.debug "#{package} package should be installed on #{host}" 17 | assert(host.check_for_package(package), "'#{package}' should be installed") 18 | logger.debug("doesnotexist package should not be installed on #{host}") 19 | assert_equal(false, host.check_for_package('doesnotexist'), '"doesnotexist" should not be installed') 20 | end 21 | 22 | step '#install_package and #uninstall_package : remove and install a package successfully' 23 | hosts.each do |host| 24 | # this works on Windows as well, althought it pulls in 25 | # a lot of dependencies. 26 | # skipping this test for windows since it requires a restart 27 | next if host['platform'].include?('windows') 28 | 29 | package = 'zsh' 30 | package = 'CSWzsh' if host['platform'].include?('solaris-10') 31 | package = 'git' if /opensuse|sles/.match?(host['platform']) 32 | 33 | if host['platform'].include?('solaris-11') 34 | logger.debug("#{package} should be uninstalled on #{host}") 35 | host.uninstall_package(package) 36 | assert_equal(false, host.check_for_package(package), "'#{package}' should not be installed") 37 | end 38 | 39 | assert_equal(false, host.check_for_package(package), "'#{package}' not should be installed") 40 | logger.debug("#{package} should be installed on #{host}") 41 | cmdline_args = '' 42 | # Newer vmpooler hosts created by Packer templates, and running Cygwin 2.4, 43 | # must have these switches passed 44 | cmdline_args = '--local-install --download' if (host['platform'].include?('windows') and host.is_cygwin?) 45 | host.install_package(package, cmdline_args) 46 | assert(host.check_for_package(package), "'#{package}' should be installed") 47 | 48 | # windows does not support uninstall_package 49 | unless host['platform'].include?('windows') 50 | logger.debug("#{package} should be uninstalled on #{host}") 51 | host.uninstall_package(package) 52 | if host['platform'].include?('debian') 53 | assert_equal(false, host.check_for_command(package), "'#{package}' should not be installed or available") 54 | else 55 | assert_equal(false, host.check_for_package(package), "'#{package}' should not be installed") 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /acceptance/tests/base/host/packages_unix.rb: -------------------------------------------------------------------------------- 1 | test_name 'confirm unix-specific package methods work' 2 | confine :except, :platform => %w(windows solaris osx) 3 | 4 | step '#update_apt_if_needed : can execute without raising an error' 5 | hosts.each do |host| 6 | host.update_apt_if_needed 7 | end 8 | -------------------------------------------------------------------------------- /acceptance/tests/base/host/user_test.rb: -------------------------------------------------------------------------------- 1 | test_name 'User Test' do 2 | step "#user_get: has an Administrator user on Windows" do 3 | hosts.select { |h| h['platform'].include?('windows') }.each do |host| 4 | host.user_get('Administrator') do |result| 5 | refute_match(result.stdout, 'NET HELPMSG', 'Output indicates Administrator not found') 6 | end 7 | end 8 | end 9 | 10 | step "#user_get: should not have CaptainCaveman user on Windows" do 11 | hosts.select { |h| h['platform'].include?('windows') }.each do |host| 12 | assert_raises Beaker::Host::CommandFailure do 13 | host.user_get('CaptainCaveman') { |result| } 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /acceptance/tests/base/host_prebuilt_steps/ssh_environment_test.rb: -------------------------------------------------------------------------------- 1 | test_name "confirm host prebuilt steps behave correctly" do 2 | confine_block :except, :platform => /windows/ do 3 | step "confirm ssh environment file existence" do 4 | hosts.each do |host| 5 | assert(host.file_exist?(host[:ssh_env_file])) 6 | end 7 | end 8 | 9 | step "confirm PATH env variable is set in the ssh environment file" do 10 | hosts.each do |host| 11 | assert_equal(0, on(host, "grep \"PATH\" #{host[:ssh_env_file]}").exit_code) 12 | end 13 | end 14 | end 15 | 16 | confine_block :to, :platform => /solaris-10/ do 17 | step "confirm /opt/csw/bin has been added to the path" do 18 | hosts.each do |host| 19 | assert_equal(0, on(host, "grep \"/opt/csw/bin\" #{host[:ssh_env_file]}").exit_code) 20 | end 21 | end 22 | end 23 | 24 | confine_block :to, :platform => /openbsd/ do 25 | step "confirm PKG_PATH is set in the ssh environment file" do 26 | hosts.each do |host| 27 | assert_equal(0, on(host, "grep \"PKG_PATH\" #{host[:ssh_env_file]}").exit_code) 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /acceptance/tests/base/test_suite/export.rb: -------------------------------------------------------------------------------- 1 | test_name 'ensure tests can export arbitrary data' do 2 | step 'export nested hash' do 3 | export({ 'middle earth' => { 4 | 'Hobbits' => %w[Bilbo Frodo], 5 | 'Elves' => 'Arwen', 6 | :total => { 'numbers' => 42 }, 7 | } }) 8 | export({ 'another' => 'author' }) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /acceptance/tests/hypervisor/communication_test.rb: -------------------------------------------------------------------------------- 1 | # hosts should be able to talk to each other by name 2 | step "hosts can ping each other" 3 | hosts.each do |one| 4 | hosts.each do |two| 5 | assert_equal(true, one.ping(two)) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /acceptance/tests/install/from_file.rb: -------------------------------------------------------------------------------- 1 | test_name 'test generic installers' 2 | 3 | confine :except, :platform => /^windows|osx/ 4 | 5 | step 'install arbitrary msi via url' do 6 | hosts.each do |host| 7 | if host['platform'].include?('win') 8 | # this should be implemented at the host/win/pkg.rb level someday 9 | generic_install_msi_on(host, 'https://releases.hashicorp.com/vagrant/1.8.4/vagrant_1.8.4.msi', {}, { :debug => true }) 10 | end 11 | end 12 | end 13 | 14 | step 'install arbitrary dmg via url' do 15 | hosts.each do |host| 16 | host.generic_install_dmg('https://releases.hashicorp.com/vagrant/1.8.4/vagrant_1.8.4.dmg', 'Vagrant', 'Vagrant.pkg') if host['platform'].include?('osx') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /acceptance/tests/load_path_bootstrap.rb: -------------------------------------------------------------------------------- 1 | # Ensure that `$LOAD_PATH` is set up properly, in cases where the entire 2 | # acceptance suite is being run, but the options file 3 | # `acceptance/config/acceptance_options.rb` was not specified via the 4 | # `--options-file` command-line argument. 5 | begin 6 | require 'helpers/test_helper' 7 | rescue LoadError 8 | $LOAD_PATH << File.expand_path(File.join(__dir__, '..', 'lib')) 9 | require 'helpers/test_helper' 10 | end 11 | -------------------------------------------------------------------------------- /acceptance/tests/subcommands/destroy.rb: -------------------------------------------------------------------------------- 1 | test_name 'use the destroy subcommand' do 2 | def delete_root_folder_contents 3 | on default, 'rm -rf /root/* /root/.beaker' 4 | end 5 | 6 | step 'ensure that `beaker destroy` fails correctly when a configuration has not been initialized' do 7 | delete_root_folder_contents 8 | result = on(default, 'beaker destroy', :accept_all_exit_codes => true) 9 | assert_match(/Please provision an environment/, result.stdout) 10 | assert_equal(1, result.exit_code, '`beaker destroy` in an uninitialised configuration should return a non-zero exit code') 11 | end 12 | 13 | step 'ensure that `beaker help destroy` works' do 14 | result = on(default, 'beaker help destroy') 15 | assert_match(/Usage/, result.stdout) 16 | assert_equal(0, result.exit_code, '`beaker help destroy` should return a zero exit code') 17 | end 18 | 19 | step 'ensure that `beaker destroy --help` works' do 20 | result = on(default, 'beaker destroy --help') 21 | assert_match(/Usage/, result.stdout) 22 | assert_equal(0, result.exit_code, '`beaker destroy --help` should return a zero exit code') 23 | end 24 | 25 | step 'ensure that `beaker destroy` destroys vmpooler configuration' do 26 | delete_root_folder_contents 27 | result = on(default, "beaker init --hosts centos6-64") 28 | assert_match(/Writing configured options to disk/, result.stdout) 29 | assert_equal(0, result.exit_code, "`beaker init` should return a zero exit code") 30 | step 'ensure destroy fails to run against an unprovisioned environment' do 31 | result = on(default, "beaker destroy", :accept_all_exit_codes => true) 32 | assert_match(/Please provision an environment/, result.stdout) 33 | assert_equal(1, result.exit_code, "`beaker destroy` should return a non zero exit code") 34 | end 35 | step 'ensure provision provisions, validates, and configures new hosts' do 36 | result = on(default, "beaker provision") 37 | assert_match(/Using available host/, result.stdout) 38 | assert_equal(0, result.exit_code, "`beaker provision` should return a zero exit code") 39 | end 40 | step 'ensure destroy will destroy a provisioned environment' do 41 | result = on(default, 'beaker destroy') 42 | assert_match(/Handing/, result.stdout) 43 | assert_equal(0, result.exit_code, "`beaker destroy` should return a zero exit code") 44 | end 45 | delete_root_folder_contents 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /acceptance/tests/subcommands/exec.rb: -------------------------------------------------------------------------------- 1 | test_name 'use the exec subcommand' do 2 | def delete_root_folder_contents 3 | on default, 'rm -rf /root/* /root/.beaker' 4 | end 5 | 6 | step 'ensure the workspace is clean' do 7 | delete_root_folder_contents 8 | end 9 | 10 | step 'run init and provision to set up the system' do 11 | on default, 'beaker init --hosts centos6-64; beaker provision' 12 | subcommand_state = on(default, 'cat .beaker/.subcommand_state.yaml').stdout 13 | subcommand_state = YAML.parse(subcommand_state).to_ruby 14 | assert_equal(true, subcommand_state['provisioned']) 15 | end 16 | 17 | step 'create a test dir and populate it with tests' do 18 | on default, 'mkdir -p testing_dir' 19 | end 20 | 21 | step 'create remote test file' do 22 | testfile = <<-TESTFILE 23 | on(agents, 'echo hello world') 24 | TESTFILE 25 | create_remote_file(default, '/root/testing_dir/testfile1.rb', testfile) 26 | end 27 | 28 | step 'specify that remote file with beaker exec' do 29 | result = on(default, 'beaker exec testing_dir/testfile1.rb --log-level verbose') 30 | assert_match(/hello world/, result.stdout) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /acceptance/tests/subcommands/init.rb: -------------------------------------------------------------------------------- 1 | test_name 'use the init subcommand' do 2 | SubcommandUtil = Beaker::Subcommands::SubcommandUtil 3 | def delete_root_folder_contents 4 | on default, 'rm -rf /root/* /root/.beaker' 5 | end 6 | 7 | step 'ensure beaker init requires hosts flag' do 8 | result = on(default, 'beaker init') 9 | assert_match(/No value(.+)--hosts/, result.raw_output) 10 | end 11 | 12 | step 'ensure beaker init writes YAML configuration files to disk' do 13 | delete_root_folder_contents 14 | on(default, 'beaker init --hosts centos6-64') 15 | subcommand_options = on(default, "cat #{SubcommandUtil::SUBCOMMAND_OPTIONS}").stdout 16 | subcommand_state = on(default, "cat #{SubcommandUtil::SUBCOMMAND_STATE}").stdout 17 | parsed_options = YAML.parse(subcommand_options).to_ruby 18 | assert(parsed_options["HOSTS"].count == 1) 19 | assert(parsed_options.instance_of?(Hash)) 20 | assert(YAML.parse(subcommand_state).to_ruby.instance_of?(Hash)) 21 | end 22 | 23 | step 'ensure beaker init saves beaker-run arguments to the subcommand_options.yaml' do 24 | delete_root_folder_contents 25 | on(default, 'beaker init --log-level verbose --hosts centos6-64') 26 | subcommand_options = on(default, "cat #{SubcommandUtil::SUBCOMMAND_OPTIONS}").stdout 27 | hash = YAML.parse(subcommand_options).to_ruby 28 | assert_equal('verbose', hash['log_level']) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /acceptance/tests/subcommands/provision.rb: -------------------------------------------------------------------------------- 1 | test_name 'use the provision subcommand' do 2 | SubcommandUtil = Beaker::Subcommands::SubcommandUtil 3 | 4 | def delete_root_folder_contents 5 | on default, 'rm -rf /root/* /root/.beaker' 6 | end 7 | 8 | step 'run beaker init and provision' do 9 | delete_root_folder_contents 10 | result = on(default, 'beaker provision --hosts centos6-64') 11 | assert_match(/ERROR(.+)--hosts/, result.raw_output) 12 | on(default, 'beaker init --hosts centos6-64') 13 | result = on(default, 'beaker provision') 14 | assert_match(/Using available host/, result.stdout) 15 | subcommand_state = on(default, "cat #{SubcommandUtil::SUBCOMMAND_STATE}").stdout 16 | subcommand_state = YAML.parse(subcommand_state).to_ruby 17 | assert_equal(true, subcommand_state['provisioned']) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /beaker.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("lib", __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'beaker/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "beaker" 7 | s.version = Beaker::Version::STRING 8 | s.authors = ["Puppet"] 9 | s.email = ["voxpupuli@groups.io"] 10 | s.homepage = "https://github.com/voxpupuli/beaker" 11 | s.summary = %q{Let's test Puppet!} 12 | s.description = %q{Puppet's accceptance testing harness} 13 | s.license = 'Apache-2.0' 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 17 | s.require_paths = ["lib"] 18 | 19 | s.required_ruby_version = Gem::Requirement.new('>= 2.7') 20 | 21 | # Testing dependencies 22 | s.add_development_dependency 'fakefs', '>= 2.4', '< 4' 23 | s.add_development_dependency 'rake', '~> 13.0' 24 | s.add_development_dependency 'rspec', '~> 3.0' 25 | s.add_development_dependency 'voxpupuli-rubocop', '~> 3.1.0' 26 | 27 | # Run time dependencies 28 | # Required for Ruby 3.3+ support 29 | s.add_dependency 'base64', '~> 0.2.0' 30 | s.add_dependency 'benchmark', '>= 0.3', '< 0.5' 31 | # we cannot require 1.0.2 because that requires Ruby 3.1 32 | s.add_dependency 'minitar', '>= 0.12', '< 2' 33 | s.add_dependency 'minitest', '~> 5.4' 34 | s.add_dependency 'rexml', '~> 3.2', '>= 3.2.5' 35 | 36 | # net-ssh compatibility with ed25519 keys 37 | s.add_dependency 'bcrypt_pbkdf', '>= 1.0', '< 2.0' 38 | s.add_dependency 'ed25519', '>= 1.2', '<2.0' 39 | 40 | s.add_dependency 'hocon', '~> 1.0' 41 | s.add_dependency 'inifile', '~> 3.0' 42 | s.add_dependency 'net-scp', '>= 1.2', '< 5.0' 43 | s.add_dependency 'net-ssh', '~> 7.1' 44 | 45 | s.add_dependency 'in-parallel', '>= 0.1', '< 2.0' 46 | s.add_dependency 'rsync', '~> 1.0.9' 47 | s.add_dependency 'thor', ['>= 1.0.1', '< 2.0'] 48 | 49 | # Run time dependencies that are Beaker libraries 50 | s.add_dependency 'beaker-hostgenerator', '~> 2.0' 51 | s.add_dependency 'stringify-hash', '~> 0.0' 52 | end 53 | -------------------------------------------------------------------------------- /bin/beaker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' unless defined?(Gem) 4 | require 'beaker' 5 | 6 | if Beaker::Subcommands::SubcommandUtil.execute_subcommand?(ARGV[0]) 7 | Beaker::Subcommand.start(ARGV) 8 | else 9 | Beaker::CLI.new.parse_options.provision.execute! 10 | puts "Beaker completed successfully, thanks." 11 | end 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ![Beaker Muppet Image](http://images4.wikia.nocookie.net/__cb20101015151248/muppet/images/0/05/Beaker.jpg) 2 | 3 | Documentation for Beaker can be found in this repository in [the docs/ folder](). 4 | 5 | ## Table of Contents 6 | 7 | - [Tutorials](tutorials) take you by the hand through the steps to setup a beaker run. Start here if you’re new to Beaker or test development. 8 | - [Concepts](concepts) discuss key topics and concepts at a fairly high level and provide useful background information and explanation. 9 | - [Rubydocs](http://rubydoc.info/github/puppetlabs/beaker/frames) contains the technical reference for APIs and other aspects of Beaker. They describe how it works and how to use it but assume that you have a basic understanding of key concepts. 10 | - [How-to guides](how_to) are recipes. They guide you through the steps involved in addressing key problems and use-cases. They are more advanced than tutorials and assume some knowledge of how Beaker works. 11 | 12 | ## Other Resources 13 | 14 | In addition to the overview above, which matches Beaker's main README docs section, this doc's README has some links to other outside resources: 15 | 16 | * [Latest Gem Release Notes](https://github.com/puppetlabs/beaker/blob/master/HISTORY.md#LATEST) 17 | * [Video: Beaker 101 talk at PDXPUG, May 2014](https://www.youtube.com/watch?v=cSyJXTYFXFg) 18 | * [Podcast: Beaker, May 2014](http://puppetlabs.com/podcasts/podcast-beaker-cloud-enabled-acceptance-testing-tool) 19 | * [Podcast: Automated Testing with Beaker for Windows, December 2014](http://puppetlabs.com/podcasts/podcast-automated-testing-beaker-windows) 20 | * [Real-world Examples: Puppetlabs Acceptance Testing for Puppet](https://github.com/puppetlabs/puppet/tree/master/acceptance/tests) 21 | -------------------------------------------------------------------------------- /docs/concepts/beaker_libraries.md: -------------------------------------------------------------------------------- 1 | # Beaker Libraries 2 | 3 | Engineering at Puppet Labs has written several libraries that extends the functionality provided by Beaker. 4 | 5 | To learn how to create beaker libraries, see the [Beaker-Template](https://github.com/puppetlabs/beaker-template/blob/master/README.md) documentation. 6 | 7 | | Name | Description | Docs | 8 | |:-------------------|:--------------------------------------------------------------------|:----------------------------------------------------------------| 9 | | Master Manipulator | Easy DSL extension for changing configuration on a Puppet Master | [Github Repo](https://github.com/puppetlabs/master_manipulator) | 10 | | beaker_windows | Useful helpers for testing on Windows hosts | [Github Repo](https://github.com/puppetlabs/beaker_windows) | 11 | | Puppet Install Helper | Use environment variables for choosing which puppet version to install | [Github Repo](https://github.com/puppetlabs/beaker-puppet_install_helper) | 12 | | testmode_switcher | [prototype] run your puppet module tests in master/agent, apply or local mode | [Github Repo](https://github.com/puppetlabs/beaker-testmode_switcher) | 13 | | beaker-hostgenerator | Generates Beaker host files | [Github Repo](https://github.com/puppetlabs/beaker-hostgenerator/) | 14 | | beaker-answers | Generates answers for Puppet Enterprise installation | [Github Repo](https://github.com/puppetlabs/beaker-answers/) | 15 | | beaker-pe | Adds helper methods for Puppet Enterprise specific tasks | [Github Repo](https://github.com/puppetlabs/beaker-pe/) | 16 | | beaker-http | Adds ability to dispatch http traffic from the coordinator | [Github Repo](https://github.com/puppetlabs/beaker-http/) | 17 | | Beaker Rubymine Plugin | An IntelliJ IDEA plugin making Beaker test runs a native IDE experience | [Github Repo](https://github.com/samwoods1/BeakerRubyMinePlugin) | 18 | | beaker-rspec | A bridge between beaker itself and [rspec](https://github.com/rspec/rspec); also integrates [serverspec](http://serverspec.org/) | [Github Repo](https://github.com/puppetlabs/beaker-rspec/) | 19 | | beaker-puppet | Adds helper & install methods for Puppet-specific tasks | [Github Repo](https://github.com/puppetlabs/beaker-puppet/) 20 | 21 | -------------------------------------------------------------------------------- /docs/concepts/glossary.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | 3 | Most terms used in Beaker documentation should be common. The following documents project jargon which may otherwise be confusing. 4 | 5 | ## Coordinator 6 | 7 | The Coordinator is the system on which Beaker itself is run. In many environments this will be a local host, typically a developer's primary machine. Used instead of Master to avoid confusion (see [Roles](/docs/concepts/roles_what_are_they.md)). 8 | 9 | ## System Under Test 10 | 11 | A System Under Test (SUT) is one of the systems which is the subject of testing with Beaker. Contrast the [Beaker Coordinator](#coordinator). 12 | -------------------------------------------------------------------------------- /docs/concepts/masterless_puppet.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## What is masterless Puppet? 4 | 5 | Masterless Puppet is a common way of running Puppet where you might have a number of Puppet Agents, but no hosts running under any other roles (master, dashboard, database, default). 6 | 7 | ## Why Would You Want to Do This? 8 | 9 | A few examples of common situations where running masterless Puppet would be useful are below: 10 | 11 | - Testing modules against Windows. Traditionally, a non-Windows master would be required, but is really just needless overhead in this case. 12 | - running Puppet to provision hosts, only running it the once, using `puppet agent`, and then providing it to your users 13 | 14 | ## How Do I Run Masterless? 15 | 16 | In order to have Beaker support a masterless Puppet setup, you have to do a few things: 17 | 18 | 1. include the `masterless: true` flag in the `CONFIG` section of your hosts file 19 | 2. Make sure the roles are correct for the hosts now. You'll want to make sure a host doesn't have a role that it won't be able to fulfill 20 | 3. Run Beaker just like you normally would 21 | 22 | # Under the Hood 23 | 24 | ## What is Beaker Doing by Default? 25 | 26 | By default (without the masterless flag), when someone calls for a host of a particular role, using the `Beaker::DSL::Roles` module's methods (ie. `master`, `dashboard`, etc), Beaker checks to verify that a host was given with that role. 27 | 28 | If no host was given with this role, then Beaker throws a `DSL::Outcomes::FailTest` Error, which causes that test case to fail. 29 | 30 | ## What Does This Flag Do? 31 | 32 | Inside Beaker, when you call `Beaker::DSL::Roles` module's methods with the masterless flag set, Beaker will allow there to be hosts which don't fit defined roles. If a host can't be found for a particular role, that role method will now return `nil`. 33 | 34 | If you'd like to test both masterless and not, you'll have to deal with a role method potentially returning `nil`. 35 | 36 | ## How Do I Avoid Issues With This? 37 | 38 | You can make it so that a test will only run if we're not running masterless with this line: 39 | 40 | confine :to, :masterless => false 41 | 42 | and vice versa. 43 | -------------------------------------------------------------------------------- /docs/concepts/roles_what_are_they.md: -------------------------------------------------------------------------------- 1 | Each host in a host configuration file is defined to have one or more roles. Beaker supports the roles `master`, `agent`, `frictionless`, `dashboard` and `database`. These roles indicate what Puppet responsibilities the host will assume. If puppet is installed as part of the Beaker test execution then the roles will be honored (ie, the host defined as `master` will become the puppet master node). Other than puppet installation, the roles provide short cuts to node access. In tests you can refer to nodes by role: 2 | 3 | on master, "echo hello" 4 | on database, "echo hello" 5 | 6 | ## Creating Your Own Roles 7 | Arbitrary role creating is supported in Beaker. New roles are created as they are discovered in the host/config file provided at runtime. 8 | 9 | ### Example User Role Creation 10 | ``` 11 | HOSTS: 12 | pe-ubuntu-lucid: 13 | roles: 14 | - agent 15 | - dashboard 16 | - database 17 | - master 18 | - nodes 19 | - ubuntu 20 | vmname : pe-ubuntu-lucid 21 | platform: ubuntu-10.04-i386 22 | snapshot : clean-w-keys 23 | hypervisor : fusion 24 | pe-centos6: 25 | roles: 26 | - agent 27 | - nodes 28 | - centos 29 | vmname : pe-centos6 30 | platform: el-6-i386 31 | hypervisor : fusion 32 | snapshot: clean-w-keys 33 | CONFIG: 34 | nfs_server: none 35 | consoleport: 443 36 | ``` 37 | 38 | In this case I've created the new roles `nodes`, `centos` and `ubuntu`. These roles can now be used to call any Beaker DSL methods that require a host. 39 | 40 | ``` 41 | on centos, 'echo I'm the centos box' 42 | on ubuntu, 'echo I'm the ubuntu box' 43 | on nodes, 'echo this command will be executed on both defined hosts' 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/concepts/shared_options_for_executing_beaker_commands.md: -------------------------------------------------------------------------------- 1 | Beaker commands are executed through an ssh connection to each individual SUT. Various options are supported during command execution that control how the commands themselves are executed, what output is generated and how results are interpreted. 2 | 3 | ## :acceptable_exit_codes 4 | 5 | Provide either a single or an array of acceptable/passing exit codes for the provided command. Defaults to the single exit code `0`. 6 | 7 | on host, puppet( 'agent -t' ), :acceptable_exit_codes => [0,1,2] 8 | 9 | ## :accept_all_exit_codes 10 | 11 | Consider any exit codes returned by the command to be acceptable/passing. Defaults to `nil`/`false`. 12 | 13 | on host, puppet( 'agent -t' ), :accept_all_exit_codes => true 14 | 15 | ## :expect_connection_failure 16 | 17 | Assume that this command will cause a connection failure. Used for `host.reboot` so that Beaker can handle the broken ssh connection. 18 | 19 | on host, "reboot", {:expect_connection_failure => true} 20 | 21 | ## :dry_run 22 | 23 | Do not actually execute this command on the SUT. Defaults to `false`. 24 | 25 | on host, "don't do this crazy thing", {:dry_run => true} 26 | 27 | ## :pty 28 | 29 | Should this command be executed in a pseudoterminal? Defaults to `false`. 30 | 31 | on host, "sudo su -c \"service ssh restart\"", {:pty => true}) 32 | 33 | ## :silent 34 | 35 | Do not output any logging for this command. Defaults to `false`. 36 | 37 | on host, "echo hello", {:silent => true} 38 | 39 | ## :stdin 40 | 41 | Specifies standard input to be provided to the command post execution. Defaults to `nil`. 42 | 43 | on host, "this command takes input", {:stdin => "hiya"} 44 | 45 | ## [:run_in_parallel](../how_to/run_in_parallel.md) 46 | 47 | Execute the command against all hosts in parallel 48 | 49 | on hosts, puppet( 'agent -t' ), :run_in_parallel => true 50 | -------------------------------------------------------------------------------- /docs/concepts/testing_beaker_itself.md: -------------------------------------------------------------------------------- 1 | # Testing Beaker Itself 2 | 3 | While Beaker provides the testing harness for much of the acceptance testing that happens at Puppet, Beaker itself must also go through a testing process for changes submitted to itself to ensure that releases of Beaker do not break pipelines, jobs, and tests that rely on it. This document describes what is actually covered in Beaker's own testing and how that testing is accomplished. 4 | 5 | ## Testing Coverage 6 | 7 | ### Product Coverage 8 | 9 | Beaker test coverage covers the LTS PE version, currently 2016.4.0, and the latest released version of PE, currently 2016.5.0. Since there is only a single major version of Puppet itself currently supported, beaker only run tests on the latest y-release of Puppet 4, currently 4.8.z. This currently resolves to puppet-agent 1.8.x. 10 | 11 | ### Platform Coverage 12 | 13 | The platforms that beaker covers in its regression testing are largely what is supported by either Puppet or Puppet Enterprise. All variants that are supported by Puppet Enterprise as master platforms are tested. Variants that are agent only are more sparsely covered, generally testing the latest released version. 14 | 15 | ## Test Suite Phases 16 | 17 | ### Beaker Spec 18 | 19 | The initial step in Beaker's pipeline is to execute spec testing with supported and future rubies; 2.2.5 and 2.3.1. 20 | 21 | ### Beaker Acceptance 22 | 23 | All acceptance tests use actual OS's with beaker installed and use beaker itself to verify that its own methods and classes are working. 24 | 25 | * The Base tests are tests that do not require puppet be installed on the SUT. This includes much of the DSL and host helpers. 26 | * The puppet tests rely on puppet being installed in the pre-suite 27 | 28 | ### Beaker Regression 29 | 30 | The Beaker regression tests are an ever evolving set of Jenkins jobs that use acceptance jobs defined in other pipelines with the Beaker PR changes. We run these jobs to ensure the PR changes do not cause breakage in existing acceptance jobs. The tests themselves are maintained by each separate team. 31 | -------------------------------------------------------------------------------- /docs/concepts/types_puppet_4_and_the_all_in_one_agent.md: -------------------------------------------------------------------------------- 1 | ## So What Are Types Anyway? 2 | 3 | Historically, Puppet Open Source and Enterprise have had different paths for different resources. Beaker supports these configurations through the use of its `type` parameter. The Beaker CLI exposes this parameter with the `--type` option. 4 | 5 | The two older types are represented by the `foss` and `pe` values. 6 | 7 | Note that if you don't provide any type, the default is [pe](https://github.com/puppetlabs/beaker/blob/master/lib/beaker/options/presets.rb#L131). 8 | 9 | ## New With Puppet 4: The AIO Type! 10 | 11 | With the introduction of the All-In-One (AIO) Agent in Puppet 4, the paths have been unified across both versions (Open Source & Enterprise). In order to support this, Beaker has added the `aio` type. 12 | 13 | Passing this argument will setup the machines to use the new AIO pathing. This should be all you need to be correctly setup to use the AIO Agent in Puppet 4 and beyond! 14 | -------------------------------------------------------------------------------- /docs/how_to/archive_sut_files.md: -------------------------------------------------------------------------------- 1 | # How to Archive Files from the Systems Under Test (SUTs) 2 | 3 | Oftentimes when you're dealing with beaker test development or troubleshooting a failed acceptance test, you'll need to get information from a SUT. The traditional way that we've advocated getting information from these machines is to use our [preserved hosts functionality](preserve_hosts.md). 4 | 5 | If you're preserving hosts just to SSH in and look at log files, however, this can be a tedious exercise. Why not just bring the log files to you on the beaker coordinator? This doc explains exactly how to do that using our `archive_file_from` Domain-Specific Language (DSL) method ([method rubydocs](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#archive_file_from-instance_method)). 6 | 7 | # How Do I Use This? 8 | 9 | `archive_file_from` is a part of the beaker DSL, so it's available in all test suites. Just call it from your tests, and it'll execute, pulling any particular file you need off your SUTs, and dropping it on your beaker coordinator's file system. 10 | 11 | A common example of a post-suite step to archive files that were created during a particular test is included in the Rubydocs, referenced above. Path details, and details of all method arguments are documented there as well. Check it out, and with the right use, you won't need to preserve hosts at all to debug any test failures. 12 | 13 | # Challenges 14 | 15 | ## Conditionally Saving Files From SUTs 16 | 17 | One thing that people tend to want from this functionality is to only archive files from SUTs when a beaker run has failed. At this point, beaker does not have access to other suites from a current one. This means that in practice, a post-suite (where one would typically put archiving and other post-processes) will not be able to archive files ONLY IF the test suite has had any failures or errors. 18 | 19 | Our suggestion to get the functionality required would be to have beaker always archive the appropriate files in the post-suite of your tests, but then only have Jenkins (or your job running system, whatever that may be) conditionally take them from the beaker coordinator to whatever external archive system you rely on for later analysis. This can both get you the files that you need from the SUTs and save on space, as only files that need analysis will be kept. 20 | 21 | # When Did This Come Out? 22 | 23 | `archive_file_from` was originally added to the DSL in beaker [2.48.0](https://github.com/puppetlabs/beaker/releases/tag/2.48.0), released on [July 27, 2016](https://github.com/puppetlabs/beaker/blob/master/HISTORY.md#2480---27-jul-2016-47d3aa18). 24 | -------------------------------------------------------------------------------- /docs/how_to/change_terminal_output_coloring.md: -------------------------------------------------------------------------------- 1 | 2 | Beaker uses a set of colors to output different types of messages on to the terminal. 3 | 4 | ## The Default Color Codes 5 | If you do not provide any values, the defaults are: [Default colors](https://github.com/puppetlabs/beaker/blob/master/lib/beaker/logger.rb#L85-L95) 6 | 7 | ## Beaker Color Codes: 8 | 9 | In addition, Beaker can support few other colors. List of all colors supported by Beaker: [Colors Supported by Beaker] (https://github.com/puppetlabs/beaker/blob/master/lib/beaker/logger.rb#L14-L32) 10 | 11 | ## How to Customize: 12 | 13 | Changes to the default options can be made by editing the configuration file. 14 | 15 | Here are some examples: 16 | 17 | **Ex 1: Changing color of a particular type of message** 18 | 19 | Add the following to the hosts file to change the color of `success` messages to `GREEN` and `warning` messages to `YELLOW`. To get the color-code corresponding to a color, refer to: [Colors Supported by Beaker] (https://github.com/puppetlabs/beaker/blob/master/lib/beaker/logger.rb#L14-L32) 20 | 21 | HOSTS: 22 | ... 23 | CONFIG: 24 | log_colors: 25 | success: "\e[01;35m" 26 | warn: "\e[00;33m" 27 | 28 | **Ex 2: Turning off colors.** 29 | 30 | The following option in the hosts file will print the whole output in one single color. 31 | 32 | HOSTS: 33 | ... 34 | CONFIG: 35 | color: false 36 | -------------------------------------------------------------------------------- /docs/how_to/cloning_private_repos.md: -------------------------------------------------------------------------------- 1 | # Cloning private repos 2 | 3 | If you need to clone private repos when running Beaker acceptance tests, please refer to the [enabling cross SUT access](enabling_cross_sut_access.md) document 4 | -------------------------------------------------------------------------------- /docs/how_to/enabling_cross_sut_access.md: -------------------------------------------------------------------------------- 1 | # Enabling access bewteen SUTs during an acceptance test run 2 | 3 | If you are running acceptance tests for Beaker that, at some point, will perform one of the following: 4 | 5 | * SSH between SUTs 6 | * Clone private repos 7 | 8 | You will need to run an SSH agent, and add the SSH key for accessing your SUTs/private repos, prior to running the tests. 9 | 10 | To load the SSH agent and add your SSH key, run the following: 11 | 12 | ~~~bash 13 | eval `ssh-agent` 14 | ssh-add 15 | ~~~ 16 | 17 | A common example of where this functionality would be required for beaker developers, is in testing subcommands. There, we setup multiple SUTs that need to communicate between themselves. To run our subcommand testing to verify that you have agent forwarding setup correctly, run the following: 18 | 19 | ~~~bash 20 | beaker --tests acceptance/tests/subcommands/ --log-level debug --preserve-hosts onfail --pre-suite acceptance/pre_suite/subcommands/ --load-path acceptance/lib --keyfile ~/.ssh/id_rsa-acceptance 21 | ~~~ 22 | 23 | And Beaker will be able to SSH between SUTs and clone private repos 24 | -------------------------------------------------------------------------------- /docs/how_to/hosts/README.md: -------------------------------------------------------------------------------- 1 | # The Hosts Directory 2 | 3 | This directory contains docs explaining any peculiarities or details of a particular OS's host implementation. 4 | 5 | If you don't see a file here for an OS, then it's either not yet documented (feel free to help us out here!), or it should conform to our normal host abstraction assumptions. -------------------------------------------------------------------------------- /docs/how_to/hosts/archlinux.md: -------------------------------------------------------------------------------- 1 | # Arch Linux 2 | 3 | > Arch Linux is an independently developed, i686/x86-64 general-purpose GNU/Linux distribution that strives to provide the latest stable versions of most software by following a rolling-release model. The default installation is a minimal base system, configured by the user to only add what is purposely required. 4 | 5 | Source: https://wiki.archlinux.org/index.php/Arch_Linux 6 | 7 | # Installation 8 | 9 | ## Specifying a version to install 10 | 11 | > Arch Linux strives to maintain the latest stable release versions of its software as long as systemic package breakage can be reasonably avoided. 12 | 13 | Source: https://wiki.archlinux.org/index.php/Arch_Linux 14 | 15 | Since Arch is a rolling release, the Puppet version installed by Pacman will always be the latest avaliable release from the upstream. 16 | 17 | Because of this, it's not possible to specify a specific version with any of the Puppet install helper methods, and a warning will be shown if it is attempted. 18 | 19 | Because the Arch version will always be latest, it will always be Puppet 4+ with the AIO packaging, so it's advised to specify this in the config: 20 | 21 | ``` 22 | CONFIG: 23 | log_level: verbose 24 | type: aio 25 | ``` 26 | 27 | ## Versioning 28 | 29 | Arch doesn't really have the idea of a release version, as it's a rolling update. 30 | 31 | For coventions sake, it's advised to put the date of creation of your Arch VM in the name of your SUT, so you know roughly when the VM is cut from: 32 | 33 | ``` 34 | HOSTS: 35 | archlinux-2016.02.02-amd64: 36 | roles: 37 | - master 38 | platform: archlinux-2016.02.02-amd64 39 | box: terrywang/archlinux 40 | box_version: 1.0.0 41 | hypervisor: vagrant 42 | CONFIG: 43 | log_level: verbose 44 | type: aio 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/how_to/hypervisors/solaris.md: -------------------------------------------------------------------------------- 1 | Used with `hypervisor: solaris`, the harness can connect to a Solaris host via SSH and revert zone snapshots. 2 | 3 | ### example .fog file ### 4 | :default: 5 | :solaris_hypervisor_server: solaris.example.com 6 | :solaris_hypervisor_username: harness 7 | :solaris_hypervisor_keyfile: /home/jenkins/.ssh/id_rsa-harness 8 | :solaris_hypervisor_vmpath: rpool/zoneds 9 | :solaris_hypervisor_snappaths: 10 | - rpool/ROOT/solaris 11 | -------------------------------------------------------------------------------- /docs/how_to/rake_tasks.md: -------------------------------------------------------------------------------- 1 | # Rake test tasks for running beaker 2 | 3 | There are some rake tasks that you can use to run Beaker tests from your local project dir. 4 | 5 | To use them from within your own project, you will need to require the following file in your project's rakefile: 6 | 7 | require 'beaker/tasks/test' 8 | 9 | You will also need to have Beaker installed as part of your bundle. 10 | 11 | When you run: 12 | 13 | rake --tasks 14 | 15 | from your project dir, you should see (as well as any rake tasks you have defined locally) 16 | 17 | rake beaker:test[hosts,type] # Run Beaker Acceptance 18 | rake beaker:test:git[hosts] # Run Beaker Git tests 19 | rake beaker:test:pe[hosts] # Run Beaker PE tests 20 | 21 | The last two tasks assume that you have an options file in `/acceptance` named `beaker-git.cfg` and `beaker-pe.cfg` respectively. 22 | 23 | Your options file would look something like: 24 | 25 | { 26 | :type => 'git', 27 | :pre_suite => ['./acceptance/setup/install.rb'], 28 | :hosts_file => './acceptance/config/windows-2012r2-x86_64.cfg', 29 | :log_level => 'debug', 30 | :tests => ['./acceptance/tests/access_rights_directory', './acceptance/tests/identity', 31 | './acceptance/tests/owner', './acceptance/tests/propagation', 32 | './acceptance/tests/use_cases', './acceptance/tests/access_rights_file', './acceptance/tests/group', 33 | './acceptance/tests/inheritance', './acceptance/tests/parameter_target', './acceptance/tests/purge'], 34 | :keyfile => '~/.ssh/id_rsa-acceptance', 35 | :timeout => 6000 36 | } 37 | 38 | To use the more generic test task, you will need to pass in the type as the 2nd argument to the rake task: 39 | 40 | rake beaker:test[,smoke] 41 | 42 | This will assume that you have created the file: 43 | 44 | acceptance/beaker-smoke.cfg 45 | 46 | -------------------------------------------------------------------------------- /docs/how_to/recipes.md: -------------------------------------------------------------------------------- 1 | # What is This? 2 | 3 | Patterns for best-use solutions to (not so) common problems 4 | 5 | ## How do i set persistent environment variables on a SUT, such as PATH? 6 | 7 | ```ruby 8 | host.add_env_var('PATH', '/opt/puppetlabs/bin:$PATH') 9 | ``` 10 | 11 | ## How do i run commands on a SUT as a non-root user? 12 | 13 | (warning) this should be abstracted into a beaker helper, or part of `on()`: BKR-168 - Beaker::DSL::Helpers needs "as" method READY FOR ENGINEERING 14 | 15 | Create the user, then `su` with `--command`: 16 | 17 | ```ruby 18 | on(host, puppet("resource user #{username} ensure=present managehome-true")) 19 | on(host, "su #{username} --command '#{command}'") 20 | ``` -------------------------------------------------------------------------------- /docs/how_to/run_in_parallel.md: -------------------------------------------------------------------------------- 1 | # run_in_parallel global and command options 2 | 3 | ## run_in_parallel global option 4 | 5 | The run_in_parallel global option is an array with the following possible values: ['configure', 'install']. It defaults to an empty array `[]`. It can be set in an options file, or overriden by the `BEAKER_RUN_IN_PARALLEL` environment variable. Example: 6 | 7 | ```console 8 | $ export BEAKER_RUN_IN_PARALLEL=configure,install 9 | ``` 10 | 11 | Including 'configure' causes timesync to execute in parallel (if timesync=true for any host) 12 | 13 | Including 'install' causes as much of the puppet install to happen in parallel as possible. 14 | 15 | ## run_in_parallel command option 16 | 17 | The run_in_parallel command option is a boolean value, specifying whether to execute each iteration (usually of hosts) in parallel, or not. The block_on method is the primary method accepting the run_in_parallel command option, however many methods that call into block_on respect it as well: 18 | 19 | - on 20 | - run_block_on 21 | - block_on 22 | - install_puppet_agent_on 23 | - apply_manifest_on 24 | - stop_agent_on 25 | - execute_powershell_script_on 26 | 27 | ## Using InParallel in your test scripts 28 | 29 | In addition to the options, you can use InParallel within your test scripts as well. Examples: 30 | 31 | ```ruby 32 | include InParallel 33 | 34 | test_name('test_test') 35 | 36 | # Example 1 37 | hosts.each_in_parallel{ |host| 38 | # Do something on each host 39 | } 40 | 41 | def some_method_call 42 | return "some_method_call" 43 | end 44 | 45 | def some_other_method_call 46 | return "some_other_method_call" 47 | end 48 | 49 | # Example 2 50 | # Runs each method within the block in parallel in a forked process 51 | run_in_parallel{ 52 | @result = some_method_call 53 | @result_2 = some_other_method_call 54 | } 55 | 56 | # results in 'some_method_callsome_other_method_call' 57 | puts @result + @result_2 58 | ``` 59 | 60 | **_Note:_** While you can return a result from a forked process to an instance variable, any values assigned to local variables, or other changes to global state will not persist from the child process to the parent process. 61 | 62 | Further documentation on the usage of [InParallel](http://github/puppetlabs/in-parallel/readme.md) 63 | -------------------------------------------------------------------------------- /docs/how_to/ssh_agent_forwarding.md: -------------------------------------------------------------------------------- 1 | # How to Forward ssh(1) Agent 2 | 3 | `ssh(1)` agent forwarding can is activated in the `CONFIG` section of the hosts file: 4 | 5 | ```yaml 6 | HOSTS: 7 | ... 8 | CONFIG: 9 | forward_ssh_agent: true 10 | ``` 11 | 12 | Beaker will then make the ssh agent running on the beaker coordinator available to the Systems Under Test (SUT). There is a gotcha though: the agent socket file in the SUT is only available to the user who signed in. If you want to access remote machine resources as another user, you *must* change the socket permission. 13 | 14 | A dirty hack is to `chmod -R 777 /tmp/ssh-*` before changing to another user and relying on `$SSH_AUTH_SOCK`. 15 | 16 | Example: 17 | 18 | ```puppet 19 | exec { '/bin/chmod -R 777 /tmp/ssh-*': 20 | } -> 21 | vcsrepo { '/var/www/app': 22 | provider => 'git', 23 | source => 'https://example.com/git/app.git', 24 | user => 'deploy' 25 | } 26 | ``` 27 | 28 | ## Cross-SUT access 29 | 30 | If you need to be able to SSH between SUTs while running Beaker acceptance tests, please refer to the [enabling cross SUT access](enabling_cross_sut_access.md) document 31 | -------------------------------------------------------------------------------- /docs/how_to/ssh_connection_preference.md: -------------------------------------------------------------------------------- 1 | # Set SSH connection methods preference 2 | 3 | Setting up a SSH connection to hosts can be tricky. Beaker supports three methods to SSH to hosts: 4 | 5 | 1. `:ip` 6 | 2. `:vmhostname` - DNS name 7 | 3. `:hostname` 8 | 9 | Beaker tries to SSH to hosts using these methods in a particular preference (order). Default preference is mentioned above. We allow hypervisor authors and end users to provide an array of these methods that reflects their preference. Note that methods are identified by Ruby symbols, not strings. 10 | 11 | ## Why set a preference? 12 | 13 | Depending upon your hypervisor, your host could have specific method that it uses to SSH better and faster than other methods. For example, hosts generated by vmpooler connects better with `vmhostname` as some change their ip adderess on restart. Therefore vmpooler hypervisor sets its connection preference to use vmhostname first. 14 | 15 | ## Setting SSH connection preference at hypervisor level 16 | 17 | Hypervisor authors can set SSH connection preference. The only thing they have to do is override the `connection_preference` method set in [hypervisor.rb](https://github.com/puppetlabs/beaker/blob/master/lib/beaker/hypervisor.rb) file in their own hypervisor file. 18 | 19 | For example, `beaker-vmpooler` overriding this in [vmpooler.rb](https://github.com/puppetlabs/beaker-vmpooler/blob/master/lib/beaker/hypervisor/vmpooler.rb) file. 20 | 21 | ## Setting SSH connection methods in hosts file 22 | 23 | End users can override the connection preference that is default or set by their hypervisor. This can be done from your hosts file. All you need to do is provide a `ssh_preference` for each host. The value of this key should be an array of the methods specified above in an order you prefer. Beaker then will attempt to SSH to the hosts in that particular order. 24 | 25 | Example of a host file: 26 | 27 | ```yaml 28 | HOSTS: 29 | ubuntu1604-64-1: 30 | hypervisor: vmpooler 31 | platform: ubuntu-16.04-amd64 32 | template: ubuntu-1604-x86_64 33 | ssh_preference: 34 | - :vmhostname 35 | - :hostname 36 | - :ip 37 | roles: 38 | - agent 39 | - default 40 | ubuntu1604-64-2: 41 | hypervisor: vmpooler 42 | platform: ubuntu-16.04-amd64 43 | template: ubuntu-1604-x86_64 44 | ssh_preference: [:ip, :vmhostname] 45 | roles: 46 | - agent 47 | CONFIG: 48 | nfs_server: none 49 | consoleport: 443 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/how_to/test_arbitrary_beaker_versions.md: -------------------------------------------------------------------------------- 1 | # Test arbitrary beaker versions without modifying test code 2 | 3 | In order to adjust the beaker version used without commiting a change to a Gemfile, we at Puppet often use a method in our code that changes the dependency based on the existence of ENV variables in the shell that beaker is executing from. The code itself looks like this: 4 | 5 | ```ruby 6 | def location_for(place, fake_version = nil) 7 | if /^(git[:@][^#]*)#(.*)/.match?(place) 8 | [fake_version, { :git => $1, :branch => $2, :require => false }].compact 9 | elsif /^file:\/\/(.*)/.match?(place) 10 | ['>= 0', { :path => File.expand_path($1), :require => false }] 11 | else 12 | [place, { :require => false }] 13 | end 14 | end 15 | ``` 16 | 17 | Once this method definition is in place in the Gemfile, we can call it in a gem command, like this: 18 | 19 | ```ruby 20 | 21 | gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '~> 2.0') 22 | ``` 23 | 24 | ## Example BEAKER_VERSIONs 25 | 26 | ### git locations 27 | 28 | ``` 29 | git@github.com:puppetlabs/beaker.git#master 30 | git://github.com/puppetlabs/beaker.git#master 31 | ``` 32 | 33 | ### file locations 34 | 35 | ``` 36 | file://../relative/path/to/beaker 37 | ``` 38 | 39 | By adjusting the shell environment that beaker is running in, we can modify what version of beaker is installed by bundler on your test coordinator without modifying any of the test code. This strategy can be used for any gem dependency, and is often used when testing [beaker libraries](../concepts/beaker_libraries.md) at Puppet. 40 | -------------------------------------------------------------------------------- /docs/how_to/use_hocon_helpers.md: -------------------------------------------------------------------------------- 1 | # How to Use Hocon Helpers 2 | 3 | Beaker provides a few convenience methods to help you use the [HOCON](https://github.com/typesafehub/config/blob/master/HOCON.md) configuration file format in your testing. This doc will give you an overview of what each method does, but if you'd like more in-depth information, please checkout our [Hocon Helpers Rubydocs](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HoconHelpers). 4 | 5 | ## hocon_file_read_on 6 | 7 | If you'd just like to read the contents of a HOCON file from a System Under Test (SUT), this is the method for you. Note that you will get back a [ConfigValueFactory object](https://github.com/puppetlabs/ruby-hocon#basic-usage) like in the other helper methods here. 8 | 9 | ## hocon_file_edit_in_place_on 10 | 11 | This method is specifically for editing a file on a SUT and saving it in-place, meaning it'll save your changes in the place of the original file you read from. 12 | 13 | The special thing to take note of here is that the Proc you pass to this method will need to return the doc that you'd like saved in order for saving to work as specified. 14 | 15 | ## hocon_file_edit_on 16 | 17 | This method is our generic open-ended method for editing a file from a SUT. This is the most flexible method, doing nothing but providing you with the contents of the file to edit yourself. 18 | 19 | This does not save the file edited. Our recommendation is to use the [`create_remote_file` method](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#create_remote_file-instance_method), as shown in the [Rubydocs example](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HoconHelpers#hocon_file_edit_on-instance_method) if you'd like to save. This allows us to have more flexibility to do things such as moving the edited file to back up or version your changes. 20 | -------------------------------------------------------------------------------- /docs/how_to/use_user_password_authentication.md: -------------------------------------------------------------------------------- 1 | By default Beaker connects to hosts using public key authentication, but that may not be correct method for your particular testing set up. To have beaker connect to a host using a username/password combination edit your hosts configuration file. You will need to create a new ssh hash to be used for logging into your SUT that includes (at least) entries for `user`, `password`, and `auth_method`. You may also include any additional supported [Net::SSH Options](http://net-ssh.github.io/ssh/v1/chapter-2.html#s3). 2 | 3 | ## Example 1: Use 'password' authentication 4 | 5 | ```yaml 6 | HOSTS: 7 | pe-centos6: 8 | roles: 9 | - master 10 | - agent 11 | - dashboard 12 | - database 13 | - myrole 14 | platform: el-6-i386 15 | snapshot: clean-w-keys 16 | hypervisor: fusion 17 | ssh: 18 | password: anode 19 | user: anode 20 | auth_methods: 21 | - password 22 | ``` 23 | 24 | The log will then read as: 25 | 26 | _snip_ 27 | ``` 28 | pe-centos6 20:19:16$ echo hello! 29 | Attempting ssh connection to pe-centos6, user: anode, opts: {:config=>false, :verify_host_key=>false, :timeout=>300, :auth_methods=>["password"], :port=>22, :forward_agent=>true, :keys=>["/Users/anode/.ssh/id_rsa"], :user_known_hosts_file=>"/Users/anode/.ssh/known_hosts", :password=>"anode", :user=>"anode"} 30 | ``` 31 | _/snip_ 32 | 33 | ## Example 2: Use a list of authentication methods 34 | 35 | If you want to try a sequence of authentication techniques that fall through on failure simply include them (in their desired order) in your list of `auth_methods`. If one of your methods is user/password be warned, after a failure Net::SSH will attempt keyboard-interactive password entry - if you do not want this behavior add `number_of_password_prompts: 0`. 36 | 37 | ```yaml 38 | HOSTS: 39 | pe-centos6: 40 | roles: 41 | - master 42 | - agent 43 | - dashboard 44 | - database 45 | - myrole 46 | platform: el-6-i386 47 | snapshot: clean-w-keys 48 | hypervisor: fusion 49 | CONFIG: 50 | ssh: 51 | auth_methods: 52 | - password 53 | - publickey 54 | number_of_password_prompts: 0 55 | password: wootwoot 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/how_to/write_a_beaker_test_for_a_module.md: -------------------------------------------------------------------------------- 1 | # Beaker for Modules 2 | 3 | ## Read the Beaker Docs 4 | 5 | - [Beaker How To](../tutorials/how_to_beaker.md) 6 | 7 | - [Beaker DSL API](http://rubydoc.info/github/puppetlabs/beaker/frames) 8 | 9 | ## Understand the Difference Between beaker and beaker-rspec 10 | 11 | [beaker vs. beaker-rspec](../concepts/beaker_vs_beaker_rspec.md) 12 | 13 | ## beaker-rspec Details 14 | 15 | See the [beaker-rspec README](https://github.com/puppetlabs/beaker-rspec/blob/master/README.md) for details on how to use beaker-rspec with modules. 16 | -------------------------------------------------------------------------------- /docs/tutorials/README.md: -------------------------------------------------------------------------------- 1 | # Tutorials 2 | 3 | This doc is here to help you get acquainted with beaker and how we run our acceptance tests at Puppet. We'll go over the purpose of each doc, giving you an idea of when you might need each one. The list has been organized as a learning guide for someone new to using beaker, so be aware of that if you're just dipping into a topic. 4 | 5 | For more high level & motivation topics, checkout our [concepts docs](../concepts). If you're looking for more details on a topic than what is provided in the tutorials, checkout our [how to docs](../how_to). And if you'd like API level details, feel free to skip on over to our [Rubydocs](http://www.rubydoc.info/github/puppetlabs/beaker/frames). And without further pre-amble, we beaker! 6 | 7 | ## Installation 8 | 9 | If you haven't installed beaker yet, your guide to doing so can be found [here](installation.md). 10 | 11 | ## Quick Start 12 | 13 | As a completely new beaker user, the [quick start rake tasks doc](quick_start_rake_tasks.md) will take you through getting beaker running for the first time. 14 | 15 | ## OK, We're Running. Now What? 16 | 17 | This is where things get interesting. There are a number of directions you can go, based on your needs. Here's a list of the common directions people take at this point. 18 | 19 | ### Test Writing 20 | 21 | Most people reading this doc are in Quality orgs, or are developers who need to get some testing done for their current work. If getting a particular bit of testing done is your next step, this is the direction for you. 22 | 23 | Checkout our [let's write a test](lets_write_a_test.md) to start with test writing! 24 | 25 | ### Running Beaker Itself 26 | 27 | For the quick start guide, we resorted to using rake tasks to get beaker running quickly and easily. In the real world, people need much more customization out of their testing environments. One of the main ways people provide these options is through command line arguments. 28 | 29 | If you want to find out more about running beaker itself, checkout [the command line](the_command_line.md). 30 | 31 | ### Environment Details 32 | 33 | If you don't need to get your tests running _anywhere_, but need them on a ton of Operating Systems (OSes), then your next stop is setting up your test environment. 34 | 35 | Our [creating a test environment doc](creating_a_test_environment.md) is the next spot for you! 36 | 37 | ### High Level Execution Details 38 | 39 | For a higher level look at what happens during beaker execution, which we call a _run_, checkout our [test run doc](test_run.md). A _run_ is an entire beaker execution cycle, from when the command is run until beaker exits. 40 | 41 | As one phase of a test run, the test suites are executed. To get more information about the test suites that are available, and how you configure them, you can check out our [test suites doc](test_suites.md). -------------------------------------------------------------------------------- /docs/tutorials/how_to_beaker.md: -------------------------------------------------------------------------------- 1 | * [Beaker Installation](installation.md) 2 | * [Creating A Test Environment](creating_a_test_environment.md) 3 | * [The Command Line](the_command_line.md) 4 | * [The Beaker DSL](../how_to/the_beaker_dsl.md) 5 | * [Let's Write a Test!](lets_write_a_test.md) 6 | * [Access The Live Test Console with Pry](../how_to/access_the_live_test_console_with_pry.md) 7 | -------------------------------------------------------------------------------- /docs/tutorials/subcommands.md: -------------------------------------------------------------------------------- 1 | # Using Subcommands 2 | 3 | The document gives an overview of the subcommands that Beaker supports and describes how to use them. 4 | 5 | ## Why Subcommands? 6 | 7 | Subcommands are designed to make test development and iteration simpler by separating out all of the phases of a Beaker [test run](test_run.md)*. Instead of requiring the entirety of your Beaker execution in one command, subcommands allow you to execute each phase independently. This allows for faster feedback for potential failures and better control for iterating over actual test development. 8 | 9 | Most subcommands pass through flags to the Beaker options. For instance, you can pass through `--hosts` to the `init` subcommand and it will parse the `--hosts` argument as if you were executing a Beaker run*. Please review the subcommand specific help for further information. You can see the help for a specific subcommand by running `beaker help SUBCOMMAND`. 10 | 11 | Please note that in this document, a Beaker `run` is standard beaker invocation without any subcommands. 12 | 13 | ## Available Subcommands 14 | 15 | ### beaker init 16 | 17 | Initializes the required `.beaker/` configuration folder. This folder contains a `subcommand_options.yaml` file that is user-facing; altering this file will alter the options for subcommand execution. 18 | 19 | ### beaker provision 20 | 21 | Provisions hosts defined in your `subcommand_options file`. You can pass the `--hosts` flag here to override any hosts provided there. 22 | 23 | ### beaker exec 24 | 25 | Run either files, directories, or beaker suites. If supplied a file or directory, that resource will be run in the context of the `tests` suite; If supplied a beaker suite, then just that suite will run. If no resource is supplied, then this command executes the suites as they are defined in the configuration. Accepts a comma-separated, homogeneous list. E.g. only files, only directories, or only suites, such as: `exec pre-suite,tests` 26 | 27 | ### beaker destroy 28 | 29 | Execute this command to deprovision your systems under test(SUTs). 30 | 31 | ## Basic workflow 32 | 33 | ```console 34 | $ beaker init -h hosts_file -o options_file --keyfile ssh_key --pre-suite ./setup/pre-suit/my_presuite.rb 35 | $ beaker provision 36 | # Note: do not pass in hosts file, or use the '-t' flag! Just the file 37 | # or directory. Do not pass GO. Do not collect $200. 38 | $ beaker exec ./tests/my_test.rb 39 | # Repeating the above command as needed 40 | # When you're done testing using the VM that Beaker provisioned 41 | $ beaker destroy 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/tutorials/test_run.md: -------------------------------------------------------------------------------- 1 | # Beaker Test Runs 2 | 3 | A Beaker test run consists of two primary phases: an SUT provision phase and a suite execution phase. After suite execution, Beaker defaults to cleaning up and destroying the SUTs. Leave SUTs running with the `--preserve-hosts` flag. 4 | 5 | ## Provisioning 6 | 7 | * Using supported hypervisors, provision SUTs for testing on 8 | * Do any initial configuration to ensure that the SUTs can communicate with beaker and each other 9 | * skip with `--no-provision` 10 | * Provisioning also runs some basic setup on the SUTs 11 | * Validation 12 | * Check the SUTs for necessary packages (curl, ntpdate) 13 | * skip with `--no-validate` 14 | * Configuration 15 | * Do any post-provisioning configuration to the SUTs 16 | * skip with `--no-configure` 17 | 18 | ## Execution 19 | 20 | * Execute the files specified in each of the suites; for further documentation, 21 | please refer to [Test Suites & Failure Modes](test_suites.md) 22 | -------------------------------------------------------------------------------- /docs/tutorials/the_command_line.md: -------------------------------------------------------------------------------- 1 | # The Command Line 2 | 3 | * Using the gem 4 | 5 | ```console 6 | $ beaker --log-level debug --hosts sample.cfg --tests test.rb 7 | ``` 8 | 9 | * Using latest git 10 | 11 | ```console 12 | $ bundle exec beaker --log-level debug --hosts sample.cfg --tests test.rb 13 | ``` 14 | 15 | ## Useful options 16 | 17 | * `-h, --hosts FILE `, the hosts that you are going to be testing with 18 | * `--log-level debug`, for providing verbose logging and full stacktraces on failure 19 | * `--[no-]provision`, indicates if beaker should provision new boxes upon test execution. If `no` is selected then beaker will attempt to connect to the hosts as defined in `--hosts FILE` without first creating/running them through their hypervisors 20 | * `--preserve-hosts [MODE]`, indicates what should be done with the testing boxes after tests are complete. If `always` is selected then the boxes will be preserved and left running post-testing. If `onfail` is selected then the boxes will be preserved only if tests fail, otherwise they will be shut down and destroyed. If `never` is selected then the boxes will be shut down and destroyed no matter the testing results. 21 | * `--parse-only`, read and parse all command line options, environment options and file options; report the parsed options and exit. 22 | 23 | ## The Rest 24 | 25 | See all options with 26 | 27 | * Using the gem 28 | 29 | ```console 30 | $ beaker --help 31 | ``` 32 | 33 | * Using latest git 34 | 35 | ```console 36 | $ bundle exec beaker --help 37 | ``` -------------------------------------------------------------------------------- /ext/completion/beaker-completion.bash: -------------------------------------------------------------------------------- 1 | ## bash completion script for beaker 2 | 3 | # The contained completion routines provide support for completing: 4 | # 5 | # *) any option specified in beaker --help 6 | # but not yet the arguments to the options 7 | 8 | # To use these routines: 9 | # 10 | # 1) Copy this file to somewhere (e.g. ~/.beaker-completion.sh). 11 | # 2) Add the following line to your .bashrc/.zshrc: 12 | # source ~/.beaker-completion.sh 13 | # 3) tab will now complete beaker's command line options 14 | 15 | _beaker_complete() 16 | { 17 | # COMP_WORDS is an array of words in the current command line. 18 | # COMP_CWORD is the index of the current word (the one the cursor is 19 | # in). So COMP_WORDS[COMP_CWORD] is the current word; we also record 20 | # the previous word here, although this specific script doesn't 21 | # use it yet. 22 | local cur_word="${COMP_WORDS[COMP_CWORD]}" 23 | local prev_word="${COMP_WORDS[COMP_CWORD-1]}" 24 | 25 | # Ask beaker to generate a list of args 26 | # ensure other warnings/errors on stderr go to null 27 | local beaker_help=`beaker --help 2>/dev/null` 28 | 29 | # parse out commands and switches 30 | # grep extended regex, only print match 31 | local dash_words=`echo "${beaker_help}" | grep -oE ' \-(\w|\[|\]|-)+' | uniq` 32 | # parse for negated commands 33 | local negative_words=`echo "${dash_words}" | grep -oE '\-\-\[\w+-\](\w|-)+' | sed 's/\[//' | sed 's/\]//'` 34 | # remove the negative portion from dash_words 35 | local dash_words=`echo "${dash_words}" | sed 's/\[no\-\]//g'` 36 | 37 | # TODO: 38 | # Parse out arguments to commands 39 | # Perform completion if the previous word is doubledash option and current word in argslist, 40 | 41 | # Perform completion if the current word starts with a dash ('-'), 42 | if [[ ${cur_word} == -* ]] ; then 43 | # COMPREPLY is the array of possible completions, generated with 44 | # the compgen builtin. 45 | COMPREPLY=( $(compgen -W "${dash_words} ${negative_words}" -- ${cur_word}) ) 46 | else 47 | COMPREPLY=() 48 | fi 49 | return 0 50 | } 51 | 52 | # Register _beaker_complete to provide completion for the following commands 53 | complete -F _beaker_complete -o default beaker pe-beaker 54 | -------------------------------------------------------------------------------- /lib/beaker.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' unless defined?(Gem) 2 | module Beaker 3 | %w(version platform test_suite test_suite_result result command options network_manager cli perf logger_junit subcommand).each do |lib| 4 | begin 5 | require "beaker/#{lib}" 6 | rescue LoadError 7 | require File.expand_path(File.join(__dir__, 'beaker', lib)) 8 | end 9 | end 10 | # These really are our sub-systems that live within the harness today 11 | # Ideally I would like to see them split out into modules that can be 12 | # included as such here 13 | # 14 | # The Testing DSL 15 | require 'beaker/dsl' 16 | # 17 | # Our Host Abstraction Layer 18 | require 'beaker/host' 19 | # 20 | # Our Hypervisor Abstraction Layer 21 | require 'beaker/hypervisor' 22 | # 23 | # How we manage connecting to hosts and hypervisors 24 | # require 'beaker/connectivity' 25 | # 26 | # Our test runner, suite, test cases and steps 27 | # require 'beaker/runner' 28 | # 29 | # Common setup and testing steps 30 | # require 'beaker/steps' 31 | 32 | # InParallel, for executing in parallel 33 | require 'in_parallel' 34 | 35 | # Shared methods and helpers 36 | require 'beaker/shared' 37 | 38 | # Minitest, for including Minitest::Assertions 39 | require 'minitest/test' 40 | end 41 | -------------------------------------------------------------------------------- /lib/beaker/command_factory.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/test' 2 | 3 | module Beaker 4 | module CommandFactory 5 | include Minitest::Assertions 6 | # Why do we need this accessor? 7 | # https://github.com/seattlerb/minitest/blob/master/lib/minitest/assertions.rb#L8-L12 8 | # Protocol: Nearly everything here boils up to +assert+, which 9 | # expects to be able to increment an instance accessor named 10 | # +assertions+. This is not provided by Assertions and must be 11 | # provided by the thing including Assertions. See Minitest::Runnable 12 | # for an example. 13 | attr_writer :assertions 14 | 15 | def assertions 16 | @assertions || 0 17 | end 18 | 19 | # Helper to create & run commands 20 | # 21 | # @note {Beaker::Host#exec} gets passed a duplicate of the options hash argument. 22 | # @note {Beaker::Command#initialize} gets passed selected options from the 23 | # options hash argument. Specifically, :prepend_cmds & :cmdexe. 24 | # 25 | # @param [String] command Command to run 26 | # @param [Hash{Symbol=>Boolean, Array}] options Options to pass 27 | # through for command execution 28 | # 29 | # @api private 30 | # @return [String] Stdout from command execution 31 | def execute(command, options = {}) 32 | cmd_create_options = {} 33 | exec_opts = options.dup 34 | cmd_create_options[:prepend_cmds] = exec_opts.delete(:prepend_cmds) || nil 35 | cmd_create_options[:cmdexe] = exec_opts.delete(:cmdexe) || false 36 | result = self.exec(Command.new(command, [], cmd_create_options), exec_opts) 37 | 38 | if block_given? 39 | yield result 40 | else 41 | result.stdout.chomp 42 | end 43 | end 44 | 45 | def fail_test(msg) 46 | assert(false, msg) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/beaker/dsl/helpers.rb: -------------------------------------------------------------------------------- 1 | %w[host test web hocon].each do |lib| 2 | require "beaker/dsl/helpers/#{lib}_helpers" 3 | end 4 | 5 | module Beaker 6 | module DSL 7 | # Contains methods to help you manage and configure your SUTs. 8 | 9 | # Extensions, available in separate modules, enable you to configure and interact with puppet, facter 10 | # and hiera. See [the docs](/docs/how_to/the_beaker_dsl.md). 11 | 12 | # To mix this is into a class you need the following: 13 | # * a method *hosts* that yields any hosts implementing 14 | # {Beaker::Host}'s interface to act upon. 15 | # * a method *options* that provides an options hash, see {Beaker::Options::OptionsHash} 16 | # * a method *logger* that yields a logger implementing 17 | # {Beaker::Logger}'s interface. 18 | # * the module {Beaker::DSL::Roles} that provides access to the various hosts implementing 19 | # {Beaker::Host}'s interface to act upon 20 | # * the module {Beaker::DSL::Wrappers} the provides convenience methods for {Beaker::DSL::Command} creation 21 | # * a method *metadata* that yields a hash 22 | # 23 | # 24 | module Helpers 25 | include Beaker::DSL::Helpers::HostHelpers 26 | include Beaker::DSL::Helpers::TestHelpers 27 | include Beaker::DSL::Helpers::WebHelpers 28 | include Beaker::DSL::Helpers::HoconHelpers 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/beaker/dsl/helpers/test_helpers.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module DSL 3 | module Helpers 4 | # Methods that help you query the state of your tests, these 5 | # methods do not require puppet to be installed to execute correctly 6 | module TestHelpers 7 | # Gets the currently executing test's name, which is set in a test 8 | # using the {Beaker::DSL::Structure#test_name} method. 9 | # 10 | # @return [String] Test name, or nil if it hasn't been set 11 | def current_test_name 12 | metadata[:case] && metadata[:case][:name] ? metadata[:case][:name] : nil 13 | end 14 | 15 | # Gets the currently executing test's filename, which is set from the 16 | # +@path+ variable passed into the {Beaker::TestCase#initialize} method, 17 | # not including the '.rb' extension 18 | # 19 | # @example if the path variable was man/plan/canal.rb, then the filename would be: 20 | # canal 21 | # 22 | # @return [String] Test filename, or nil if it hasn't been set 23 | def current_test_filename 24 | metadata[:case] && metadata[:case][:file_name] ? metadata[:case][:file_name] : nil 25 | end 26 | 27 | # Gets the currently executing test's currently executing step name. 28 | # This is set using the {Beaker::DSL::Structure#step} method. 29 | # 30 | # @return [String] Step name, or nil if it hasn't been set 31 | def current_step_name 32 | metadata[:step] && metadata[:step][:name] ? metadata[:step][:name] : nil 33 | end 34 | 35 | # Sets the currently executing test's name. 36 | # 37 | # @param [String] name Name of the test 38 | # 39 | # @return nil 40 | # @api private 41 | def set_current_test_name(name) 42 | metadata[:case] ||= {} 43 | metadata[:case][:name] = name 44 | end 45 | 46 | # Sets the currently executing test's filename. 47 | # 48 | # @param [String] filename Name of the file being tested 49 | # 50 | # @return nil 51 | # @api private 52 | def set_current_test_filename(filename) 53 | metadata[:case] ||= {} 54 | metadata[:case][:file_name] = filename 55 | end 56 | 57 | # Sets the currently executing step's name. 58 | # 59 | # @param [String] name Name of the step 60 | # 61 | # @return nil 62 | # @api private 63 | def set_current_step_name(name) 64 | metadata[:step] ||= {} 65 | metadata[:step][:name] = name 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/beaker/dsl/install_utils.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module DSL 3 | # Collection of installation methods and support 4 | module InstallUtils 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/beaker/dsl/patterns.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module DSL 3 | # These are simple patterns that appear frequently in beaker test 4 | # code, and are provided to simplify test construction. 5 | # 6 | # 7 | # It requires the class it is mixed into to provide the attribute 8 | # `hosts` which contain the hosts to search, these should implement 9 | # {Beaker::Host}'s interface. They, at least, must have #[] 10 | # and #to_s available and provide an array when #[]('roles') is called. 11 | # 12 | module Patterns 13 | # Execute a block selecting the hosts that match with the provided criteria 14 | # @param [Array, Host, String, Symbol] hosts_or_filter A host role as a String or Symbol that can be 15 | # used to search for a set of Hosts, a host name 16 | # as a String that can be used to search for 17 | # a set of Hosts, or a {Host} 18 | # or Array<{Host}> to run the block against 19 | # @param [Hash{Symbol=>String}] opts Options to alter execution. 20 | # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel. 21 | # @param [Block] block This method will yield to a block of code passed by the caller 22 | # 23 | # @return [Array, Result, nil] An array of results, a result object, or nil. 24 | # Check {Beaker::Shared::HostManager#run_block_on} for more details on this. 25 | def block_on hosts_or_filter, opts = {}, &block 26 | block_hosts = nil 27 | if defined? hosts 28 | block_hosts = hosts 29 | end 30 | filter = nil 31 | if hosts_or_filter.is_a? String or hosts_or_filter.is_a? Symbol 32 | filter = hosts_or_filter 33 | else 34 | block_hosts = hosts_or_filter 35 | end 36 | run_block_on block_hosts, filter, opts, &block 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/beaker/host/aix.rb: -------------------------------------------------------------------------------- 1 | %w[host command_factory].each do |lib| 2 | require "beaker/#{lib}" 3 | end 4 | 5 | module Aix 6 | class Host < Unix::Host 7 | %w[user group file exec].each do |lib| 8 | require "beaker/host/aix/#{lib}" 9 | end 10 | 11 | include Aix::User 12 | include Aix::Group 13 | include Aix::File 14 | include Aix::Exec 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/beaker/host/aix/exec.rb: -------------------------------------------------------------------------------- 1 | module Aix::Exec 2 | include Beaker::CommandFactory 3 | 4 | def reboot 5 | exec(Beaker::Command.new("shutdown -Fr"), :expect_connection_failure => true) 6 | end 7 | 8 | def get_ip 9 | execute("ifconfig -a inet| awk '/broadcast/ {print $2}' | cut -d/ -f1 | head -1").strip 10 | end 11 | 12 | # Restarts the SSH service 13 | # 14 | # @return [Result] result of starting ssh service 15 | def ssh_service_restart 16 | exec(Beaker::Command.new("stopsrc -g ssh")) 17 | exec(Beaker::Command.new("startsrc -g ssh")) 18 | end 19 | 20 | # Sets the PermitUserEnvironent setting & restarts the SSH service 21 | # 22 | # @api private 23 | # @return [Result] result of the command starting the SSH service 24 | # (from {#ssh_service_restart}). 25 | def ssh_permit_user_environment 26 | exec(Beaker::Command.new("echo '\nPermitUserEnvironment yes' >> /etc/ssh/sshd_config")) 27 | ssh_service_restart 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/beaker/host/aix/file.rb: -------------------------------------------------------------------------------- 1 | module Aix::File 2 | include Beaker::CommandFactory 3 | 4 | def tmpfile(name = '', extension = nil) 5 | execute("rndnum=${RANDOM} && touch /tmp/#{name}.${rndnum}#{extension} && echo /tmp/#{name}.${rndnum}#{extension}") 6 | end 7 | 8 | def tmpdir(name = '') 9 | execute("rndnum=${RANDOM} && mkdir /tmp/#{name}.${rndnum} && echo /tmp/#{name}.${rndnum}") 10 | end 11 | 12 | def path_split(paths) 13 | paths.split(':') 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/beaker/host/aix/group.rb: -------------------------------------------------------------------------------- 1 | module Aix::Group 2 | include Beaker::CommandFactory 3 | 4 | def group_list 5 | execute("lsgroup -a ALL") do |result| 6 | yield result if block_given? 7 | 8 | result.stdout.lines.map(&:strip) 9 | end 10 | end 11 | 12 | def group_get(name) 13 | execute("lsgroup #{name}") do |result| 14 | fail_test "failed to get group #{name}" unless /^#{name} id/.match?(result.stdout) 15 | 16 | yield result if block_given? 17 | result 18 | end 19 | end 20 | 21 | def group_gid(name) 22 | execute("lsgroup -a id #{name}") do |result| 23 | # Format is: 24 | # staff id=500 25 | result.stdout.split('=').last.strip 26 | end 27 | end 28 | 29 | def group_present(name, &block) 30 | execute("if ! lsgroup #{name}; then mkgroup #{name}; fi", {}, &block) 31 | end 32 | 33 | def group_absent(name, &block) 34 | execute("if lsgroup #{name}; then rmgroup #{name}; fi", {}, &block) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/beaker/host/aix/user.rb: -------------------------------------------------------------------------------- 1 | module Aix::User 2 | include Beaker::CommandFactory 3 | 4 | def user_list 5 | execute("lsuser ALL") do |result| 6 | users = [] 7 | result.stdout.each_line do |line| 8 | users << line.split(' ')[0] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | users 14 | end 15 | end 16 | 17 | def user_get(name) 18 | execute("lsuser #{name}") do |result| 19 | fail_test "failed to get user #{name}" unless /^#{name} id/.match?(result.stdout) 20 | 21 | yield result if block_given? 22 | result 23 | end 24 | end 25 | 26 | def user_present(name, &block) 27 | execute("if ! lsuser #{name}; then mkuser #{name}; fi", {}, &block) 28 | end 29 | 30 | def user_absent(name, &block) 31 | execute("if lsuser #{name}; then rmuser #{name}; fi", {}, &block) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/beaker/host/freebsd.rb: -------------------------------------------------------------------------------- 1 | %w[host command_factory].each do |lib| 2 | require "beaker/#{lib}" 3 | end 4 | 5 | module FreeBSD 6 | class Host < Unix::Host 7 | %w[ 8 | exec 9 | pkg 10 | ].each do |lib| 11 | require "beaker/host/freebsd/#{lib}" 12 | end 13 | 14 | include FreeBSD::Exec 15 | include FreeBSD::Pkg 16 | 17 | def platform_defaults 18 | h = Beaker::Options::OptionsHash.new 19 | h.merge({ 20 | 'user' => 'root', 21 | 'group' => 'root', 22 | 'pathseparator' => ':', 23 | }) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/beaker/host/freebsd/exec.rb: -------------------------------------------------------------------------------- 1 | module FreeBSD::Exec 2 | include Beaker::CommandFactory 3 | 4 | def echo_to_file(str, filename) 5 | # FreeBSD gets weird about special characters, we have to go a little OTT here 6 | escaped_str = str.gsub(/\t/, '\\t').gsub(/\n/, '\\n') 7 | 8 | exec(Beaker::Command.new("printf \"#{escaped_str}\" > #{filename}")) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/beaker/host/freebsd/pkg.rb: -------------------------------------------------------------------------------- 1 | module FreeBSD::Pkg 2 | include Beaker::CommandFactory 3 | 4 | def pkg_info_pattern(package) 5 | # This seemingly restrictive pattern prevents false positives... 6 | "^#{package}-[0-9][0-9a-zA-Z_\\.,]*$" 7 | end 8 | 9 | def check_pkgng_sh 10 | 'TMPDIR=/dev/null ASSUME_ALWAYS_YES=1 PACKAGESITE=file:///nonexist ' \ 11 | 'pkg info -x "pkg(-devel)?\\$" > /dev/null 2>&1' 12 | end 13 | 14 | def pkgng_active?(opts = {}) 15 | opts = { :accept_all_exit_codes => true }.merge(opts) 16 | execute("/bin/sh -c '#{check_pkgng_sh}'", opts) { |r| r }.exit_code == 0 17 | end 18 | 19 | def install_package(package, cmdline_args = nil, _version = nil, opts = {}) 20 | cmd = if pkgng_active? 21 | "pkg install #{cmdline_args || '-y'} #{package}" 22 | else 23 | "pkg_add #{cmdline_args || '-r'} #{package}" 24 | end 25 | execute(cmd, opts) { |result| result } 26 | end 27 | 28 | def uninstall_package(package, cmdline_args = nil, opts = {}) 29 | cmd = if pkgng_active? 30 | "pkg delete #{cmdline_args || '-y'} #{package}" 31 | else 32 | "pkg_delete #{cmdline_args || '-r'} #{package}" 33 | end 34 | execute(cmd, opts) { |result| result } 35 | end 36 | 37 | def check_for_package(package, opts = {}) 38 | opts = { :accept_all_exit_codes => true }.merge(opts) 39 | cmd = if pkgng_active? 40 | "pkg info #{package}" 41 | else 42 | "pkg_info -Ix '#{pkg_info_pattern(package)}'" 43 | end 44 | execute(cmd, opts) { |result| result }.exit_code == 0 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/beaker/host/mac.rb: -------------------------------------------------------------------------------- 1 | %w[host command_factory command options].each do |lib| 2 | require "beaker/#{lib}" 3 | end 4 | 5 | module Mac 6 | class Host < Unix::Host 7 | %w[exec user group pkg].each do |lib| 8 | require "beaker/host/mac/#{lib}" 9 | end 10 | 11 | include Mac::Exec 12 | include Mac::User 13 | include Mac::Group 14 | include Mac::Pkg 15 | 16 | def platform_defaults 17 | h = Beaker::Options::OptionsHash.new 18 | h.merge({ 19 | 'user' => 'root', 20 | 'group' => 'root', 21 | 'pathseparator' => ':', 22 | }) 23 | end 24 | 25 | attr_reader :external_copy_base 26 | 27 | def initialize name, host_hash, options 28 | super 29 | 30 | @external_copy_base = '/var/root' 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/beaker/host/mac/exec.rb: -------------------------------------------------------------------------------- 1 | module Mac::Exec 2 | include Beaker::CommandFactory 3 | 4 | def touch(file, abs = true) 5 | (abs ? '/usr/bin/touch' : 'touch') + " #{file}" 6 | end 7 | 8 | # Restarts the SSH service 9 | # 10 | # @return [Result] result of starting SSH service 11 | def ssh_service_restart 12 | launch_daemons_plist = '/System/Library/LaunchDaemons/ssh.plist' 13 | exec(Beaker::Command.new("launchctl unload #{launch_daemons_plist}")) 14 | exec(Beaker::Command.new("launchctl load #{launch_daemons_plist}")) 15 | end 16 | 17 | # Sets the PermitUserEnvironment setting & restarts the SSH service 18 | # 19 | # @api private 20 | # @return [Result] result of the command starting the SSH service 21 | # (from {#ssh_service_restart}) 22 | def ssh_permit_user_environment 23 | ssh_config_file = '/etc/sshd_config' 24 | ssh_config_file = '/private/etc/ssh/sshd_config' if /^osx-/.match?(self['platform']) 25 | 26 | exec(Beaker::Command.new("echo '\nPermitUserEnvironment yes' >> #{ssh_config_file}")) 27 | ssh_service_restart 28 | end 29 | 30 | #  Checks if selinux is enabled 31 | # selinux is not availble on OS X 32 | # 33 | # @return [Boolean] false 34 | def selinux_enabled? 35 | false 36 | end 37 | 38 | # Update ModifiedDate on a file 39 | # @param [String] file Path to the file 40 | # @param [String] timestamp Timestamp to set 41 | def modified_at(file, timestamp = nil) 42 | require 'date' 43 | time = timestamp ? DateTime.parse("#{timestamp}") : DateTime.now 44 | timestamp = time.strftime('%Y%m%d%H%M') 45 | execute("touch -mt #{timestamp} #{file}") 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/beaker/host/pswindows.rb: -------------------------------------------------------------------------------- 1 | ['host', 'command_factory', 'command', 'options', 'dsl/wrappers'].each do |lib| 2 | require "beaker/#{lib}" 3 | end 4 | 5 | module PSWindows 6 | class Host < Windows::Host 7 | %w[user group exec pkg file].each do |lib| 8 | require "beaker/host/pswindows/#{lib}" 9 | end 10 | 11 | include PSWindows::User 12 | include PSWindows::Group 13 | include PSWindows::File 14 | include PSWindows::Exec 15 | include PSWindows::Pkg 16 | 17 | def external_copy_base 18 | return @external_copy_base if @external_copy_base 19 | 20 | @external_copy_base = execute('for %I in (%ALLUSERSPROFILE%) do @echo %~I') 21 | @external_copy_base 22 | end 23 | 24 | # attr_reader :network_separator, :external_copy_base, :system_temp_path 25 | attr_reader :scp_separator, :system_temp_path 26 | 27 | def initialize name, host_hash, options 28 | super 29 | 30 | @scp_separator = '/' 31 | # %TEMP% == C:\Users\ADMINI~1\AppData\Local\Temp 32 | # is a user temp path, not the system path. Also, it doesn't work, there's 33 | # probably an issue with the `ADMINI~1` section 34 | @system_temp_path = 'C:\\Windows\\Temp' 35 | @external_copy_base = nil 36 | # @external_copy_base = '/programdata' 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/beaker/host/pswindows/file.rb: -------------------------------------------------------------------------------- 1 | module PSWindows::File 2 | include Beaker::CommandFactory 3 | 4 | def tmpfile(_name = '', extension = nil) 5 | if extension 6 | # TODO: I do not have access to Windows, but the internet suggests this 7 | # $newname = [System.IO.Path]::ChangeExtension($filename, "#{extension}") ; MoveItem $filename $newname 8 | raise NotImplementedError, 'Passing an extension is not implemented' 9 | end 10 | 11 | result = exec(powershell('[System.IO.Path]::GetTempFileName()')) 12 | result.stdout.chomp 13 | end 14 | 15 | def tmpdir(name = '') 16 | tmp_path = exec(powershell('[System.IO.Path]::GetTempPath()')).stdout.chomp 17 | 18 | name = exec(powershell('[System.IO.Path]::GetRandomFileName()')).stdout.chomp if name == '' 19 | exec(powershell("New-Item -Path '#{tmp_path}' -Force -Name '#{name}' -ItemType 'directory'")) 20 | File.join(tmp_path, name) 21 | end 22 | 23 | def path_split(paths) 24 | paths.split(';') 25 | end 26 | 27 | def cat(path) 28 | exec(powershell("type #{path}")).stdout 29 | end 30 | 31 | def file_exist?(path) 32 | result = exec(Beaker::Command.new("if exist #{path} echo true"), accept_all_exit_codes: true) 33 | result.stdout.strip == 'true' 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/beaker/host/pswindows/group.rb: -------------------------------------------------------------------------------- 1 | module PSWindows::Group 2 | include Beaker::CommandFactory 3 | 4 | def group_list 5 | execute('cmd /c echo "" | wmic group where localaccount="true" get name /format:value') do |result| 6 | groups = [] 7 | result.stdout.each_line do |line| 8 | groups << (line.match(/^Name=(.+)$/) or next)[1] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | groups 14 | end 15 | end 16 | 17 | def group_get(name) 18 | execute("net localgroup \"#{name}\"") do |result| 19 | fail_test "failed to get group #{name}" if result.exit_code != 0 20 | 21 | yield result if block_given? 22 | result 23 | end 24 | end 25 | 26 | def group_gid(_name) 27 | raise NotImplementedError, "Can't retrieve group gid on a Windows host" 28 | end 29 | 30 | def group_present(name, &block) 31 | execute("net localgroup /add \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 32 | end 33 | 34 | def group_absent(name, &block) 35 | execute("net localgroup /delete \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/beaker/host/pswindows/pkg.rb: -------------------------------------------------------------------------------- 1 | module PSWindows::Pkg 2 | include Beaker::CommandFactory 3 | 4 | def check_for_command(name) 5 | result = exec(Beaker::Command.new("where #{name}"), :accept_all_exit_codes => true) 6 | result.exit_code == 0 7 | end 8 | 9 | def check_for_package(_name) 10 | # HACK: NOOP 11 | # raise "Cannot check for package #{name} on #{self}" 12 | 0 13 | end 14 | 15 | def install_package(_name, _cmdline_args = '') 16 | # HACK: NOOP 17 | # raise "Package #{name} cannot be installed on #{self}" 18 | 0 19 | end 20 | 21 | def uninstall_package(_name, _cmdline_args = '') 22 | # HACK: NOOP 23 | # raise "Package #{name} cannot be uninstalled on #{self}" 24 | 0 25 | end 26 | 27 | # Examine the host system to determine the architecture, overrides default host determine_if_x86_64 so that wmic is used 28 | # @return [Boolean] true if x86_64, false otherwise 29 | def determine_if_x86_64 30 | identify_windows_architecture.include?('64') 31 | end 32 | 33 | private 34 | 35 | # @api private 36 | def identify_windows_architecture 37 | arch = nil 38 | execute("wmic os get osarchitecture", :accept_all_exit_codes => true) do |result| 39 | arch = if result.exit_code == 0 40 | result.stdout.include?('64') ? '64' : '32' 41 | else 42 | identify_windows_architecture_from_os_name_for_win2003 43 | end 44 | end 45 | arch 46 | end 47 | 48 | # @api private 49 | def identify_windows_architecture_from_os_name_for_win2003 50 | arch = nil 51 | execute("wmic os get name", :accept_all_exit_codes => true) do |result| 52 | arch = result.stdout.include?('64') ? '64' : '32' 53 | end 54 | arch 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/beaker/host/pswindows/user.rb: -------------------------------------------------------------------------------- 1 | module PSWindows::User 2 | include Beaker::CommandFactory 3 | 4 | def user_list 5 | execute('cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value') do |result| 6 | users = [] 7 | result.stdout.each_line do |line| 8 | users << (line.match(/^Name=(.+)/) or next)[1] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | users 14 | end 15 | end 16 | 17 | def user_get(name) 18 | execute("net user \"#{name}\"") do |result| 19 | fail_test "failed to get user #{name}" if result.exit_code != 0 20 | 21 | yield result if block_given? 22 | result 23 | end 24 | end 25 | 26 | def user_present(name, &block) 27 | execute("net user /add \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 28 | end 29 | 30 | def user_absent(name, &block) 31 | execute("net user /delete \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/beaker/host/unix.rb: -------------------------------------------------------------------------------- 1 | %w[host command_factory command options].each do |lib| 2 | require "beaker/#{lib}" 3 | end 4 | 5 | module Unix 6 | class Host < Beaker::Host 7 | %w[user group exec pkg file].each do |lib| 8 | require "beaker/host/unix/#{lib}" 9 | end 10 | 11 | include Unix::User 12 | include Unix::Group 13 | include Unix::File 14 | include Unix::Exec 15 | include Unix::Pkg 16 | 17 | def platform_defaults 18 | h = Beaker::Options::OptionsHash.new 19 | h.merge({ 20 | 'user' => 'root', 21 | 'group' => 'root', 22 | 'pathseparator' => ':', 23 | }) 24 | end 25 | 26 | # Determines which SSH Server is in use on this host 27 | # 28 | # @note This method is mostly a placeholder method, since only :openssh 29 | # can be returned at this time. Checkout {Windows::Host#determine_ssh_server} 30 | # for an example where work needs to be done to determine the answer 31 | # 32 | # @return [Symbol] Value for the SSH Server in use 33 | def determine_ssh_server 34 | :openssh 35 | end 36 | 37 | def external_copy_base 38 | @external_copy_base ||= begin 39 | if self['platform'].variant == 'solaris' && self['platform'].version == '10' 40 | # Solaris 10 uses / as the root user directory. Solaris 11 uses /root (like most). 41 | '/' 42 | else 43 | '/root' 44 | end 45 | end 46 | end 47 | 48 | # Tells you whether a host platform supports beaker's 49 | # {Beaker::HostPrebuiltSteps#set_env} method 50 | # 51 | # @return [String,nil] Reason message if set_env should be skipped, 52 | # nil if it should run. 53 | def skip_set_env? 54 | nil 55 | end 56 | 57 | def initialize name, host_hash, options 58 | super 59 | 60 | @external_copy_base = nil 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/beaker/host/unix/group.rb: -------------------------------------------------------------------------------- 1 | module Unix::Group 2 | include Beaker::CommandFactory 3 | 4 | def group_list 5 | execute("getent group") do |result| 6 | groups = [] 7 | result.stdout.each_line do |line| 8 | groups << (line.match(/^([^:]+)/) or next)[1] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | groups 14 | end 15 | end 16 | 17 | def group_get(name) 18 | execute("getent group #{name}") do |result| 19 | fail_test "failed to get group #{name}" unless /^#{name}:.*:[0-9]+:/.match?(result.stdout) 20 | 21 | yield result if block_given? 22 | result 23 | end 24 | end 25 | 26 | def group_gid(name) 27 | execute("getent group #{name}") do |result| 28 | # Format is: 29 | # wheel:x:10:root 30 | result.stdout.split(':')[2] 31 | end 32 | end 33 | 34 | def group_present(name, &block) 35 | execute("if ! getent group #{name}; then groupadd #{name}; fi", {}, &block) 36 | end 37 | 38 | def group_absent(name, &block) 39 | execute("if getent group #{name}; then groupdel #{name}; fi", {}, &block) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/beaker/host/unix/user.rb: -------------------------------------------------------------------------------- 1 | module Unix::User 2 | include Beaker::CommandFactory 3 | 4 | def user_list 5 | execute("getent passwd") do |result| 6 | users = [] 7 | result.stdout.each_line do |line| 8 | users << (line.match(/^([^:]+)/) or next)[1] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | users 14 | end 15 | end 16 | 17 | def user_get(name) 18 | execute("getent passwd #{name}") do |result| 19 | fail_test "failed to get user #{name}" unless /^#{name}:/.match?(result.stdout) 20 | 21 | yield result if block_given? 22 | result 23 | end 24 | end 25 | 26 | def user_present(name, &block) 27 | execute("if ! getent passwd #{name}; then useradd #{name}; fi", {}, &block) 28 | end 29 | 30 | def user_absent(name, &block) 31 | execute("if getent passwd #{name}; then userdel #{name}; fi", {}, &block) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/beaker/host/windows.rb: -------------------------------------------------------------------------------- 1 | %w[host command_factory command options].each do |lib| 2 | require "beaker/#{lib}" 3 | end 4 | 5 | module Windows 6 | # A windows host with cygwin tools installed 7 | class Host < Unix::Host 8 | %w[user group exec pkg file].each do |lib| 9 | require "beaker/host/windows/#{lib}" 10 | end 11 | 12 | include Windows::User 13 | include Windows::Group 14 | include Windows::File 15 | include Windows::Exec 16 | include Windows::Pkg 17 | 18 | def platform_defaults 19 | h = Beaker::Options::OptionsHash.new 20 | h.merge({ 21 | 'user' => 'Administrator', 22 | 'group' => 'Administrators', 23 | 'pathseparator' => ';', 24 | }) 25 | end 26 | 27 | def external_copy_base 28 | return @external_copy_base if @external_copy_base 29 | 30 | @external_copy_base = execute('echo `cygpath -smF 35`/') 31 | @external_copy_base 32 | end 33 | 34 | # Determines which SSH Server is in use on this host 35 | # 36 | # @return [Symbol] Value for the SSH Server in use 37 | # (:bitvise or :openssh at this point). 38 | def determine_ssh_server 39 | return @ssh_server if @ssh_server 40 | 41 | @ssh_server = :openssh 42 | status = execute('cmd.exe /c sc query BvSshServer', :accept_all_exit_codes => true) 43 | if status&.include?('4 RUNNING') 44 | @ssh_server = :bitvise 45 | else 46 | status = execute('cmd.exe /c sc qc sshd', :accept_all_exit_codes => true) 47 | @ssh_server = :win32_openssh if status&.include?('C:\\Windows\\System32\\OpenSSH\\sshd.exe') 48 | end 49 | @ssh_server 50 | end 51 | 52 | attr_reader :scp_separator 53 | 54 | def initialize name, host_hash, options 55 | super 56 | 57 | @ssh_server = nil 58 | @scp_separator = '\\' 59 | @external_copy_base = nil 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/beaker/host/windows/file.rb: -------------------------------------------------------------------------------- 1 | module Windows::File 2 | include Beaker::CommandFactory 3 | 4 | def tmpfile(name = '', extension = nil) 5 | execute("cygpath -m $(mktemp -t #{name}.XXXXXX#{extension})") 6 | end 7 | 8 | def tmpdir(name = '') 9 | execute("cygpath -m $(mktemp -td #{name}.XXXXXX)") 10 | end 11 | 12 | def system_temp_path 13 | # under CYGWIN %TEMP% may not be set 14 | tmp_path = execute('ECHO %SYSTEMROOT%', :cmdexe => true) 15 | tmp_path.delete("\n") + '\\TEMP' 16 | end 17 | 18 | # (see {Beaker::Host::Unix::File#chown}) 19 | # @note Cygwin's `chown` implementation does not support 20 | # windows-, DOS-, or mixed-style paths, only UNIX/POSIX-style. 21 | # This method simply wraps the normal Host#chown call with 22 | # a call to cygpath to sanitize input. 23 | def chown(user, path, recursive = false) 24 | cygpath = execute("cygpath -u #{path}") 25 | super(user, cygpath, recursive) 26 | end 27 | 28 | # (see {Beaker::Host::Unix::File#chgrp}) 29 | # @note Cygwin's `chgrp` implementation does not support 30 | # windows-, DOS-, or mixed-style paths, only UNIX/POSIX-style. 31 | # This method simply wraps the normal Host#chgrp call with 32 | # a call to cygpath to sanitize input. 33 | def chgrp(group, path, recursive = false) 34 | cygpath = execute("cygpath -u #{path}") 35 | super(group, cygpath, recursive) 36 | end 37 | 38 | # Not needed on windows 39 | def chmod(mod, path, recursive = false); end 40 | 41 | # (see {Beaker::Host::Unix::File#ls_ld}) 42 | # @note Cygwin's `ls_ld` implementation does not support 43 | # windows-, DOS-, or mixed-style paths, only UNIX/POSIX-style. 44 | # This method simply wraps the normal Host#ls_ld call with 45 | # a call to cygpath to sanitize input. 46 | def ls_ld(path) 47 | cygpath = execute("cygpath -u #{path}") 48 | super(cygpath) 49 | end 50 | 51 | # Updates a file path for use with SCP, depending on the SSH Server 52 | # 53 | # @note This will fail with an SSH server that is not OpenSSL or BitVise. 54 | # 55 | # @param [String] path Path to be changed 56 | # 57 | # @return [String] Path updated for use by SCP 58 | def scp_path(path) 59 | case determine_ssh_server 60 | when :bitvise 61 | # swap out separators 62 | path.gsub('\\', scp_separator) 63 | when :openssh 64 | path 65 | when :win32_openssh 66 | path.tr('\\', '/') 67 | else 68 | raise ArgumentError, "windows/file.rb:scp_path: ssh server not recognized: '#{determine_ssh_server}'" 69 | end 70 | end 71 | 72 | def path_split(paths) 73 | paths.split(';') 74 | end 75 | 76 | def file_exist?(path) 77 | result = exec(Beaker::Command.new("test -e '#{path}'"), :acceptable_exit_codes => [0, 1]) 78 | result.exit_code == 0 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/beaker/host/windows/group.rb: -------------------------------------------------------------------------------- 1 | module Windows::Group 2 | include Beaker::CommandFactory 3 | 4 | def group_list 5 | execute('cmd /c echo "" | wmic group where localaccount="true" get name /format:value') do |result| 6 | groups = [] 7 | result.stdout.each_line do |line| 8 | groups << (line.match(/^Name=(.+)$/) or next)[1] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | groups 14 | end 15 | end 16 | 17 | # using powershell commands as wmic is deprecated in windows 2025 18 | def group_list_using_powershell 19 | execute('cmd /c echo "" | powershell.exe "Get-LocalGroup | Select-Object -ExpandProperty Name"') do |result| 20 | groups = [] 21 | result.stdout.each_line do |line| 22 | groups << line.strip or next 23 | end 24 | 25 | yield result if block_given? 26 | 27 | groups 28 | end 29 | end 30 | 31 | def group_get(name) 32 | execute("net localgroup \"#{name}\"") do |result| 33 | fail_test "failed to get group #{name}" if result.exit_code != 0 34 | 35 | yield result if block_given? 36 | result 37 | end 38 | end 39 | 40 | def group_gid(_name) 41 | raise NotImplementedError, "Can't retrieve group gid on a Windows host" 42 | end 43 | 44 | def group_present(name, &block) 45 | execute("net localgroup /add \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 46 | end 47 | 48 | def group_absent(name, &block) 49 | execute("net localgroup /delete \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/beaker/host/windows/pkg.rb: -------------------------------------------------------------------------------- 1 | module Windows::Pkg 2 | include Beaker::CommandFactory 3 | 4 | def check_for_command(name) 5 | result = exec(Beaker::Command.new("which #{name}"), :accept_all_exit_codes => true) 6 | result.exit_code == 0 7 | end 8 | 9 | def check_for_package(name) 10 | result = exec(Beaker::Command.new("cygcheck #{name}"), :accept_all_exit_codes => true) 11 | result.exit_code == 0 12 | end 13 | 14 | def install_package(name, cmdline_args = '') 15 | arch = identify_windows_architecture 16 | 17 | if arch == '64' 18 | rootdir = "c:\\\\cygwin64" 19 | cygwin = "setup-x86_64.exe" 20 | else # 32 bit version 21 | rootdir = "c:\\\\cygwin" 22 | cygwin = "setup-x86.exe" 23 | end 24 | 25 | execute("#{cygwin} -q -n -N -d -R #{rootdir} -s http://cygwin.osuosl.org -P #{name} #{cmdline_args}") 26 | end 27 | 28 | def uninstall_package(name, _cmdline_args = '') 29 | raise "Package #{name} cannot be uninstalled on #{self}" 30 | end 31 | 32 | # Examine the host system to determine the architecture, overrides default host determine_if_x86_64 so that wmic is used 33 | # @return [Boolean] true if x86_64, false otherwise 34 | def determine_if_x86_64 35 | identify_windows_architecture.include?('64') 36 | end 37 | 38 | private 39 | 40 | # @api private 41 | def identify_windows_architecture 42 | platform.arch.include?('64') ? '64' : '32' 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/beaker/host/windows/user.rb: -------------------------------------------------------------------------------- 1 | module Windows::User 2 | include Beaker::CommandFactory 3 | 4 | def user_list 5 | execute('cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value') do |result| 6 | users = [] 7 | result.stdout.each_line do |line| 8 | users << (line.match(/^Name=(.+)/) or next)[1] 9 | end 10 | 11 | yield result if block_given? 12 | 13 | users 14 | end 15 | end 16 | 17 | # using powershell commands as wmic is deprecated in windows 2025 18 | def user_list_using_powershell 19 | execute('cmd /c echo "" | powershell.exe "Get-LocalUser | Select-Object -ExpandProperty Name"') do |result| 20 | users = [] 21 | result.stdout.each_line do |line| 22 | users << line.strip or next 23 | end 24 | 25 | yield result if block_given? 26 | 27 | users 28 | end 29 | end 30 | 31 | def user_get(name) 32 | execute("net user \"#{name}\"") do |result| 33 | fail_test "failed to get user #{name}" if result.exit_code != 0 34 | 35 | yield result if block_given? 36 | result 37 | end 38 | end 39 | 40 | def user_present(name, &block) 41 | execute("net user /add \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 42 | end 43 | 44 | def user_absent(name, &block) 45 | execute("net user /delete \"#{name}\"", { :acceptable_exit_codes => [0, 2] }, &block) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/beaker/hypervisor/noop.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | class Noop < Beaker::Hypervisor 3 | def initialize(hosts, options) 4 | super 5 | 6 | @logger = options[:logger] 7 | end 8 | 9 | def validate 10 | # noop 11 | end 12 | 13 | def configure 14 | # noop 15 | end 16 | 17 | def proxy_package_manager 18 | # noop 19 | end 20 | 21 | def provision 22 | # noop 23 | end 24 | 25 | def cleanup 26 | # noop 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/beaker/local_connection.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | 3 | module Beaker 4 | class LocalConnection 5 | attr_accessor :logger, :hostname, :ip 6 | 7 | def initialize options = {} 8 | @logger = options[:logger] 9 | @ssh_env_file = File.expand_path(options[:ssh_env_file]) 10 | @hostname = 'localhost' 11 | @ip = '127.0.0.1' 12 | @options = options 13 | end 14 | 15 | def self.connect options = {} 16 | connection = new options 17 | connection.connect 18 | connection 19 | end 20 | 21 | def connect _options = {} 22 | @logger.debug "Local connection, no connection to start" 23 | end 24 | 25 | def close 26 | @logger.debug "Local connection, no connection to close" 27 | end 28 | 29 | def with_env(env) 30 | backup = ENV.to_hash 31 | ENV.replace(env) 32 | yield 33 | ensure 34 | ENV.replace(backup) 35 | end 36 | 37 | def execute command, _options = {}, stdout_callback = nil, _stderr_callback = stdout_callback 38 | result = Result.new(@hostname, command) 39 | envs = {} 40 | if File.readable?(@ssh_env_file) 41 | File.foreach(@ssh_env_file) do |line| 42 | key, value = line.split('=') 43 | envs[key] = value 44 | end 45 | end 46 | 47 | begin 48 | clean_env = ENV.reject { |k| /^BUNDLE|^RUBY|^GEM/.match?(k) } 49 | 50 | with_env(clean_env) do 51 | std_out, std_err, status = Open3.capture3(envs, command) 52 | result.stdout << std_out 53 | result.stderr << std_err 54 | result.exit_code = status.exitstatus 55 | @logger.info(result.stdout) unless result.stdout.empty? 56 | @logger.info(result.stderr) unless result.stderr.empty? 57 | end 58 | rescue => e 59 | result.stderr << e.inspect 60 | @logger.info(result.stderr) 61 | result.exit_code = 1 62 | end 63 | 64 | result.finalize! 65 | @logger.last_result = result 66 | result 67 | end 68 | 69 | def scp_to(source, target, _options = {}) 70 | result = Result.new(@hostname, [source, target]) 71 | begin 72 | FileUtils.cp_r source, target 73 | rescue Errno::ENOENT => e 74 | @logger.warn "#{e.class} error in cp'ing. Forcing the connection to close, which should " \ 75 | "raise an error." 76 | end 77 | 78 | result.stdout << " CP'ed file #{source} to #{target}" 79 | result.exit_code = 0 80 | result 81 | end 82 | 83 | def scp_from(source, target, options = {}) 84 | scp_to(target, source, options) 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/beaker/options.rb: -------------------------------------------------------------------------------- 1 | %w(validator options_hash presets command_line_parser options_file_parser hosts_file_parser parser subcommand_options_file_parser).each do |lib| 2 | require "beaker/options/#{lib}" 3 | end 4 | -------------------------------------------------------------------------------- /lib/beaker/options/options_file_parser.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | module Beaker 3 | module Options 4 | # A set of functions to read options files 5 | module OptionsFileParser 6 | # Eval the contents of options_file_path, return as an OptionsHash 7 | # 8 | # Options file is assumed to contain extra options stored in a Hash 9 | # 10 | # ie, 11 | # { 12 | # :debug => true, 13 | # :tests => "test.rb", 14 | # } 15 | # 16 | # @param [String] options_file_path The path to the options file 17 | # 18 | # @example 19 | # options_hash = OptionsFileParser.parse_options_file('sample.cfg') 20 | # options_hash == {:debug=>true, :tests=>"test.rb", :pre_suite=>["pre-suite.rb"], :post_suite=>"post_suite1.rb,post_suite2.rb"} 21 | # 22 | # @return [OptionsHash] The contents of the options file as an OptionsHash 23 | # @raise [ArgumentError] Raises if options_file_path is not a path to a file 24 | # @note Since the options_file is Eval'ed, any other Ruby commands will also be executed, this can be used 25 | # to set additional environment variables 26 | def self.parse_options_file(options_file_path) 27 | result = Beaker::Options::OptionsHash.new 28 | if options_file_path 29 | options_file_path = File.expand_path(options_file_path) 30 | raise ArgumentError, "Specified options file '#{options_file_path}' does not exist!" unless File.exist?(options_file_path) 31 | 32 | # This eval will allow the specified options file to have access to our 33 | # scope. It is important that the variable 'options_file_path' is 34 | # accessible, because some existing options files (e.g. puppetdb) rely on 35 | # that variable to determine their own location (for use in 'require's, etc.) 36 | result = result.merge(eval(File.read(options_file_path))) 37 | end 38 | result 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/beaker/options/options_hash.rb: -------------------------------------------------------------------------------- 1 | require 'stringify-hash' 2 | 3 | module Beaker 4 | module Options 5 | # A hash that treats Symbol and String keys interchangeably 6 | # and recursively merges hashes 7 | class OptionsHash < StringifyHash 8 | # Determine if type of ObjectHash is pe, defaults to true 9 | # 10 | # @example Use this method to test if the :type setting is pe 11 | # a['type'] = 'pe' 12 | # a.is_pe? == true 13 | # 14 | # @return [Boolean] 15 | def is_pe? 16 | self[:type] ? self[:type].include?('pe') : true 17 | end 18 | 19 | # Determine the puppet type of the ObjectHash 20 | # 21 | # Default is FOSS 22 | # 23 | # @example Use this method to test if the :type setting is pe 24 | # a['type'] = 'pe' 25 | # a.get_type == :pe 26 | # 27 | # @return [Symbol] the type given in the options 28 | def get_type 29 | case self[:type] 30 | when /pe/ 31 | :pe 32 | else 33 | :foss 34 | end 35 | end 36 | 37 | def dump_to_file(output_file) 38 | dirname = File.dirname(output_file) 39 | FileUtils.mkdir_p(dirname) unless File.directory?(dirname) 40 | File.write(output_file, dump) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/beaker/options/subcommand_options_file_parser.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module Options 3 | # A set of functions to read options files 4 | module SubcommandOptionsParser 5 | def self.parse_options_file(options_file_path) 6 | result = OptionsHash.new 7 | result = YAML.load_file(options_file_path) if File.exist?(options_file_path) 8 | result 9 | end 10 | 11 | # @return [OptionsHash, Hash] returns an empty OptionHash or loads subcommand options yaml 12 | # from disk 13 | def self.parse_subcommand_options(argv, options_file) 14 | result = OptionsHash.new 15 | if Beaker::Subcommands::SubcommandUtil.execute_subcommand?(argv[0]) 16 | return result if argv[0] == 'init' 17 | 18 | result = SubcommandOptionsParser.parse_options_file(options_file) 19 | end 20 | result 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/beaker/result.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | class Result 3 | attr_accessor :host, :cmd, :exit_code, :stdout, :stderr, :output, 4 | :raw_stdout, :raw_stderr, :raw_output 5 | 6 | def initialize(host, cmd) 7 | @host = host 8 | @cmd = cmd 9 | @stdout = '' 10 | @stderr = '' 11 | @output = '' 12 | @exit_code = nil 13 | end 14 | 15 | # Ruby assumes chunked data (like something it receives from Net::SSH) 16 | # to be binary (ASCII-8BIT). We need to gather all chunked data and then 17 | # re-encode it as the default encoding it assumes for external text 18 | # (ie our test files and the strings they're trying to match Net::SSH's 19 | # output from) 20 | # This is also the lowest overhead place to normalize line endings, IIRC 21 | def finalize! 22 | @raw_stdout = @stdout 23 | @stdout = normalize_line_endings(convert(@stdout)) 24 | @raw_stderr = @stderr 25 | @stderr = normalize_line_endings(convert(@stderr)) 26 | @raw_output = @output 27 | @output = normalize_line_endings(convert(@output)) 28 | end 29 | 30 | def normalize_line_endings string 31 | return string.gsub(/\r\n?/, "\n") 32 | end 33 | 34 | def convert string 35 | # Remove invalid and undefined UTF-8 character encodings 36 | string.to_s.force_encoding('UTF-8') 37 | string.to_s.chars.select { |i| i.valid_encoding? }.join 38 | end 39 | 40 | def log(logger) 41 | logger.debug "Exited: #{exit_code}" unless exit_code == 0 or !exit_code 42 | end 43 | 44 | def formatted_output(limit = 10) 45 | @output.split("\n").last(limit).collect { |x| "\t" + x }.join("\n") 46 | end 47 | 48 | def exit_code_in?(range) 49 | range.include?(@exit_code) 50 | end 51 | 52 | def success? 53 | exit_code == 0 54 | end 55 | end 56 | 57 | class NullResult < Result 58 | def initialize(host, cmd) 59 | super(host, cmd) 60 | @exit_code = 0 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/beaker/shared.rb: -------------------------------------------------------------------------------- 1 | %w[repetition error_handler host_manager timed semvar options_resolver fog_credentials].each do |lib| 2 | require "beaker/shared/#{lib}" 3 | end 4 | module Beaker 5 | module Shared 6 | include Beaker::Shared::ErrorHandler 7 | include Beaker::Shared::HostManager 8 | include Beaker::Shared::Repetition 9 | include Beaker::Shared::Timed 10 | include Beaker::Shared::Semvar 11 | include Beaker::Shared::OptionsResolver 12 | include Beaker::Shared::FogCredentials 13 | end 14 | end 15 | include Beaker::Shared 16 | -------------------------------------------------------------------------------- /lib/beaker/shared/error_handler.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module Shared 3 | module ErrorHandler 4 | def report_and_raise(logger, e, msg) 5 | logger.error "Failed: errored in #{msg}" 6 | logger.error(e.inspect) 7 | bt = e.backtrace 8 | logger.pretty_backtrace(bt).each_line do |line| 9 | logger.error(line) 10 | end 11 | raise e 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/beaker/shared/fog_credentials.rb: -------------------------------------------------------------------------------- 1 | require 'stringify-hash' 2 | 3 | module Beaker 4 | module Shared 5 | # A set of functions to read .fog files 6 | module FogCredentials 7 | # Constructs ArgumentError with common phrasing for #get_fog_credentials errors 8 | # 9 | # @param path [String] path to offending file 10 | # @param from_env [String] if the path was overridden in ENV 11 | # @param reason [String] explanation for the failure 12 | # @return [ArgumentError] ArgumentError with preformatted message 13 | def fog_credential_error(path = nil, from_env = nil, reason = nil) 14 | message = "Failed loading credentials from .fog file" 15 | message << " '#{path}'" if path 16 | message << " #{from_env}" if from_env 17 | message << "." 18 | message << "Reason: #{reason}" if reason 19 | ArgumentError.new(message) 20 | end 21 | 22 | # Load credentials from a .fog file 23 | # 24 | # @note Loaded .fog files may use symbols for keys. 25 | # Although not clearly documented, it is valid: 26 | # https://www.rubydoc.info/gems/fog-core/1.42.0/Fog#credential-class_method 27 | # https://github.com/fog/fog-core/blob/7865ef77ea990fd0d085e49c28e15957b7ce0d2b/spec/utils_spec.rb#L11 28 | # 29 | # @param fog_file_path [String] dot fog path. Overridden by ENV["FOG_RC"] 30 | # @param credential_group [String, Symbol] Credential group to use. Overridden by ENV["FOG_CREDENTIAL"] 31 | # @return [StringifyHash] credentials stored in fog_file_path 32 | # @raise [ArgumentError] when the credentials cannot be loaded, describing the reson 33 | def get_fog_credentials(fog_file_path = '~/.fog', credential_group = :default) 34 | # respect file location from env 35 | if ENV["FOG_RC"] 36 | fog_file_path = ENV["FOG_RC"] 37 | from_env = ' set in ENV["FOG_RC"]' 38 | end 39 | begin 40 | fog = YAML.load_file(fog_file_path) 41 | rescue Psych::SyntaxError, Errno::ENOENT => e 42 | raise fog_credential_error fog_file_path, from_env, "(#{e.class}) #{e.message}" 43 | end 44 | raise fog_credential_error fog_file_path, from_env, "is empty." if fog == false # YAML.load => false for empty file 45 | 46 | # transparently support symbols or strings for keys 47 | fog = StringifyHash.new.merge!(fog) 48 | # respect credential from env 49 | # @note ENV must be a string, e.g. "default" not ":default" 50 | credential_group = ENV["FOG_CREDENTIAL"].to_sym if ENV["FOG_CREDENTIAL"] 51 | raise fog_credential_error fog_file_path, from_env, "could not load the specified credential group '#{credential_group}'." if not fog[credential_group] 52 | 53 | fog[credential_group] 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/beaker/shared/options_resolver.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module Shared 3 | # Methods for parsing options. 4 | module OptionsResolver 5 | # parses local and global options to determine if a particular mode should 6 | # be run in parallel. typically, local_options will specify a true/false 7 | # value, while global_options will specify an array of mode names that should 8 | # be run in parallel. the value specified in local_options will take precedence 9 | # over the values specified in global_options. 10 | # @param [Hash] local_options local options for running in parallel 11 | # @option local_options [Boolean] :run_in_parallel flag for running in parallel 12 | # @param [Hash] global_options global options for running in parallel 13 | # @option global_options [Array] :run_in_parallel list of modes to run in parallel 14 | # @param [String] mode the mode we want to query global_options for 15 | # @return [Boolean] true if the specified mode is in global_options and :run_in_parallel in local_options is not false, 16 | # or if :run_in_parallel in local_options is true, false otherwise 17 | # @example 18 | # run_in_parallel?({:run_in_parallel => true}) 19 | # -> will return true 20 | # 21 | # run_in_parallel?({:run_in_parallel => true}, {:run_in_parallel => ['install','configure']}, 'install') 22 | # -> will return true 23 | # 24 | # run_in_parallel?({:run_in_parallel => false}, {:run_in_parallel => ['install','configure']}, 'install') 25 | # -> will return false 26 | def run_in_parallel?(local_options = nil, global_options = nil, mode = nil) 27 | run_in_parallel = local_options[:run_in_parallel] unless local_options.nil? 28 | 29 | run_in_parallel = false if !run_in_parallel.nil? && run_in_parallel.is_a?(Array) 30 | 31 | run_in_parallel = global_options[:run_in_parallel].include?(mode) if run_in_parallel.nil? && global_options && global_options[:run_in_parallel].is_a?(Array) 32 | 33 | run_in_parallel 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/beaker/shared/repetition.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module Shared 3 | module Repetition 4 | def repeat_for seconds, &block 5 | # do not peg CPU if &block takes less than 1 second 6 | repeat_for_and_wait seconds, 1, &block 7 | end 8 | 9 | def repeat_for_and_wait seconds, wait 10 | timeout = Time.now + seconds 11 | done = false 12 | until done or timeout < Time.now 13 | done = yield 14 | sleep wait unless done 15 | end 16 | return done 17 | end 18 | 19 | def repeat_fibonacci_style_for attempts 20 | done = false 21 | attempt = 1 22 | last_wait, wait = 0, 1 23 | while not done and attempt <= attempts 24 | done = yield 25 | attempt += 1 26 | sleep wait unless done 27 | last_wait, wait = wait, last_wait + wait 28 | end 29 | return done 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/beaker/shared/timed.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module Shared 3 | module Timed 4 | def run_and_report_duration 5 | start = Time.now 6 | yield 7 | Time.now - start 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/beaker/subcommands/subcommand_util.rb: -------------------------------------------------------------------------------- 1 | require 'json' 2 | require 'stringio' 3 | require 'yaml/store' 4 | require 'fileutils' 5 | 6 | module Beaker 7 | module Subcommands 8 | # Methods used in execution of Subcommands 9 | # - should we execute a subcommand? 10 | # - sanitize options for saving as json 11 | # - exit with a specific message 12 | # - capture stdout and stderr 13 | module SubcommandUtil 14 | CONFIG_DIR = ".beaker" 15 | SUBCOMMAND_OPTIONS = Pathname("#{CONFIG_DIR}/subcommand_options.yaml") 16 | SUBCOMMAND_STATE = Pathname("#{CONFIG_DIR}/.subcommand_state.yaml") 17 | PERSISTED_HOSTS = Pathname("#{CONFIG_DIR}/.hosts.yaml") 18 | PERSISTED_HYPERVISORS = Pathname("#{CONFIG_DIR}/.hypervisors.yaml") 19 | # These options should not be part of persisted subcommand state 20 | UNPERSISTED_OPTIONS = %i[beaker_version command_line hosts_file logger password_prompt timestamp] 21 | 22 | def self.execute_subcommand?(arg0) 23 | return false if arg0.nil? 24 | 25 | (Beaker::Subcommand.instance_methods(false) << :help).include? arg0.to_sym 26 | end 27 | 28 | def self.prune_unpersisted(options) 29 | UNPERSISTED_OPTIONS.each do |unpersisted_key| 30 | options.each do |key, value| 31 | if key == unpersisted_key 32 | options.delete(key) 33 | elsif value.is_a?(Hash) 34 | options[key] = self.prune_unpersisted(value) unless value.empty? 35 | end 36 | end 37 | end 38 | options 39 | end 40 | 41 | def self.sanitize_options_for_save(options) 42 | # God help us, the YAML library won't stop adding tags to objects, so this 43 | # hack is a way to force the options into the basic object types so that 44 | # an eventual YAML.dump or .to_yaml call doesn't add tags. 45 | # Relevant stackoverflow: http://stackoverflow.com/questions/18178098/how-do-i-have-ruby-yaml-dump-a-hash-subclass-as-a-simple-hash 46 | JSON.parse(prune_unpersisted(options).to_json) 47 | end 48 | 49 | # Print a message to the console and exit with specified exit code, defaults to 1 50 | #  @param [String] msg the message to output 51 | # @param [Hash] options to specify exit code or output stack trace 52 | def self.error_with(msg, options = {}) 53 | puts msg 54 | puts options[:stack_trace] if options[:stack_trace] 55 | exit_code = options[:exit_code] ? options[:exit_code] : 1 56 | exit(exit_code) 57 | end 58 | 59 | # Execute a task but capture stdout and stderr to a buffer 60 | def self.with_captured_output 61 | begin 62 | old_stdout = $stdout.clone 63 | old_stderr = $stderr.clone 64 | $stdout = StringIO.new 65 | $stderr = StringIO.new 66 | yield 67 | ensure 68 | $stdout = old_stdout 69 | $stderr = old_stderr 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/beaker/tasks/test.rb: -------------------------------------------------------------------------------- 1 | require 'beaker/tasks/rake_task' 2 | 3 | Beaker::Tasks::RakeTask.new do |t, args| 4 | t.type = args[:type] 5 | t.hosts = args[:hosts] 6 | end 7 | 8 | desc "Run Beaker PE tests" 9 | Beaker::Tasks::RakeTask.new("beaker:test:pe", :hosts) do |t, args| 10 | t.type = 'pe' 11 | t.hosts = args[:hosts] 12 | end 13 | 14 | desc "Run Beaker Git tests" 15 | Beaker::Tasks::RakeTask.new("beaker:test:git", :hosts) do |t, args| 16 | t.type = 'git' 17 | t.hosts = args[:hosts] 18 | end 19 | -------------------------------------------------------------------------------- /lib/beaker/version.rb: -------------------------------------------------------------------------------- 1 | module Beaker 2 | module Version 3 | STRING = '6.7.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # this file is the base rubocop config for beaker + all beaker plugins 3 | require: 4 | - rubocop-performance 5 | - rubocop-rake 6 | - rubocop-rspec 7 | 8 | AllCops: 9 | NewCops: enable 10 | DisplayCopNames: true 11 | ExtraDetails: true 12 | DisplayStyleGuide: true 13 | TargetRubyVersion: '2.7' 14 | Exclude: 15 | - vendor/**/* 16 | - .vendor/**/* 17 | 18 | # this currently doesn't work with the way we handle our secrets 19 | Gemspec/RequireMFA: 20 | Enabled: false 21 | 22 | # current Vox Pupuli default is to use `add_development_dependency` in the gemspec 23 | Gemspec/DevelopmentDependencies: 24 | Enabled: false 25 | 26 | Style/TrailingCommaInHashLiteral: 27 | Enabled: True 28 | EnforcedStyleForMultiline: consistent_comma 29 | 30 | Style/TrailingCommaInArrayLiteral: 31 | Enabled: True 32 | EnforcedStyleForMultiline: consistent_comma 33 | 34 | Style/TrailingCommaInArguments: 35 | Enabled: True 36 | EnforcedStyleForMultiline: comma 37 | 38 | Metrics: 39 | Enabled: false 40 | -------------------------------------------------------------------------------- /spec/beaker/dsl/helpers/test_helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class ClassMixedWithDSLHelpers 4 | include Beaker::DSL::Helpers 5 | include Beaker::DSL::Wrappers 6 | include Beaker::DSL::Roles 7 | include Beaker::DSL::Patterns 8 | 9 | def logger 10 | RSpec::Mocks::Double.new('logger').as_null_object 11 | end 12 | end 13 | 14 | describe ClassMixedWithDSLHelpers do 15 | let(:metadata) { @metadata ? @metadata : {} } 16 | 17 | describe '#current_test_name' do 18 | it 'returns nil if the case is undefined' do 19 | allow(subject).to receive(:metadata).and_return(metadata) 20 | expect(subject.current_test_name).to be_nil 21 | end 22 | 23 | it 'returns nil if the name is undefined' do 24 | @metadata = { :case => {} } 25 | allow(subject).to receive(:metadata).and_return(metadata) 26 | expect(subject.current_test_name).to be_nil 27 | end 28 | 29 | it 'returns the set value' do 30 | name = 'holyGrail_testName' 31 | @metadata = { :case => { :name => name } } 32 | allow(subject).to receive(:metadata).and_return(metadata) 33 | expect(subject.current_test_name).to be === name 34 | end 35 | end 36 | 37 | describe '#current_test_filename' do 38 | it 'returns nil if the case is undefined' do 39 | allow(subject).to receive(:metadata).and_return(metadata) 40 | expect(subject.current_test_filename).to be_nil 41 | end 42 | 43 | it 'returns nil if the name is undefined' do 44 | @metadata = { :case => {} } 45 | allow(subject).to receive(:metadata).and_return(metadata) 46 | expect(subject.current_test_filename).to be_nil 47 | end 48 | 49 | it 'returns the set value' do 50 | name = 'holyGrail_testFilename' 51 | @metadata = { :case => { :file_name => name } } 52 | allow(subject).to receive(:metadata).and_return(metadata) 53 | expect(subject.current_test_filename).to be === name 54 | end 55 | end 56 | 57 | describe '#current_step_name' do 58 | it 'returns nil if the step is undefined' do 59 | allow(subject).to receive(:metadata).and_return(metadata) 60 | expect(subject.current_step_name).to be_nil 61 | end 62 | 63 | it 'returns nil if the name is undefined' do 64 | @metadata = { :step => {} } 65 | allow(subject).to receive(:metadata).and_return(metadata) 66 | expect(subject.current_step_name).to be_nil 67 | end 68 | 69 | it 'returns the set value' do 70 | name = 'holyGrail_stepName' 71 | @metadata = { :step => { :name => name } } 72 | allow(subject).to receive(:metadata).and_return(metadata) 73 | expect(subject.current_step_name).to be === name 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/beaker/dsl/outcomes_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class ClassMixedWithDSLOutcomes 4 | include Beaker::DSL::Outcomes 5 | end 6 | 7 | describe ClassMixedWithDSLOutcomes do 8 | let(:logger) { double } 9 | 10 | before { allow(subject).to receive(:logger).and_return(logger) } 11 | 12 | describe '#pass_test' do 13 | it "logs the notification passed to it and raises PassTest" do 14 | expect(logger).to receive(:notify).with(/blah/) 15 | expect { subject.pass_test('blah') } 16 | .to raise_error Beaker::DSL::Outcomes::PassTest 17 | end 18 | end 19 | 20 | describe '#skip_test' do 21 | it "logs the notification passed to it and raises SkipTest" do 22 | expect(logger).to receive(:notify).with(/blah/) 23 | expect { subject.skip_test('blah') } 24 | .to raise_error Beaker::DSL::Outcomes::SkipTest 25 | end 26 | end 27 | 28 | describe '#pending_test' do 29 | it "logs the notification passed to it and raises PendingTest" do 30 | expect(logger).to receive(:warn).with(/blah/) 31 | expect { subject.pending_test('blah') } 32 | .to raise_error Beaker::DSL::Outcomes::PendingTest 33 | end 34 | end 35 | 36 | describe '#fail_test' do 37 | it "logs the notification passed to it and raises FailTest" do 38 | expect(logger).to receive(:warn) 39 | expect(logger).to receive(:pretty_backtrace) 40 | expect { subject.fail_test('blah') } 41 | .to raise_error Beaker::DSL::Outcomes::FailTest 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/beaker/host/aix_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Aix 4 | describe Host do 5 | let(:options) { @options ? @options : {} } 6 | let(:platform) do 7 | if @platform 8 | { :platform => Beaker::Platform.new(@platform) } 9 | else 10 | { :platform => Beaker::Platform.new('aix-vers-arch-extra') } 11 | end 12 | end 13 | let(:host) { make_host('name', options.merge(platform)) } 14 | 15 | describe '#ssh_service_restart' do 16 | it 'invokes the correct commands on the host' do 17 | expect(Beaker::Command).to receive(:new).with('stopsrc -g ssh').once.ordered 18 | expect(Beaker::Command).to receive(:new).with('startsrc -g ssh').once.ordered 19 | host.ssh_service_restart 20 | end 21 | end 22 | 23 | describe '#ssh_permit_user_environment' do 24 | it 'calls echo to set PermitUserEnvironment' do 25 | expect(Beaker::Command).to receive(:new).with(/^echo\ /).once.ordered 26 | allow(host).to receive(:ssh_service_restart) 27 | host.ssh_permit_user_environment 28 | end 29 | 30 | it 'uses the correct ssh config file' do 31 | expect(Beaker::Command).to receive(:new).with(/#{Regexp.escape(' >> /etc/ssh/sshd_config')}$/).once 32 | allow(host).to receive(:ssh_service_restart) 33 | host.ssh_permit_user_environment 34 | end 35 | end 36 | 37 | describe '#reboot' do 38 | it 'invokes the correct command on the host' do 39 | expect(Beaker::Command).to receive(:new).with('shutdown -Fr').once 40 | host.reboot 41 | end 42 | end 43 | 44 | describe '#get_ip' do 45 | it 'invokes the correct command on the host' do 46 | expect(host).to receive(:execute).with(/^ifconfig\ \-a\ inet\|\ /).once.and_return('') 47 | host.get_ip 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/beaker/host/freebsd/exec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | describe FreeBSD::Exec do 5 | class FreeBSDExecTest 6 | include FreeBSD::Exec 7 | 8 | def initialize(hash, logger) 9 | @hash = hash 10 | @logger = logger 11 | end 12 | 13 | def [](k) 14 | @hash[k] 15 | end 16 | 17 | def to_s 18 | "me" 19 | end 20 | end 21 | 22 | let(:opts) { @opts || {} } 23 | let(:logger) { double('logger').as_null_object } 24 | let(:instance) { FreeBSDExecTest.new(opts, logger) } 25 | 26 | context "echo_to_file" do 27 | it "runs the correct echo command" do 28 | expect(Beaker::Command).to receive(:new).with('printf "127.0.0.1\tlocalhost localhost.localdomain\n10.255.39.23\tfreebsd-10-x64\n" > /etc/hosts').and_return('') 29 | expect(instance).to receive(:exec).with('').and_return(generate_result("hello", { :exit_code => 0 })) 30 | instance.echo_to_file('127.0.0.1\tlocalhost localhost.localdomain\n10.255.39.23\tfreebsd-10-x64\n', '/etc/hosts') 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/beaker/host/mac/exec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | describe Mac::Exec do 5 | class MacExecTest 6 | include Mac::Exec 7 | 8 | def initialize(hash, logger) 9 | @hash = hash 10 | @logger = logger 11 | end 12 | 13 | def [](k) 14 | @hash[k] 15 | end 16 | 17 | def to_s 18 | "me" 19 | end 20 | end 21 | 22 | let(:opts) { @opts || {} } 23 | let(:logger) { double('logger').as_null_object } 24 | let(:instance) { MacExecTest.new(opts, logger) } 25 | 26 | describe '#selinux_enabled?' do 27 | it 'does not call selinuxenabled' do 28 | expect(Beaker::Command).not_to receive(:new).with("sudo selinuxenabled") 29 | expect(instance).not_to receive(:exec).with(0, :accept_all_exit_codes => true) 30 | expect(instance.selinux_enabled?).to be === false 31 | end 32 | end 33 | 34 | describe '#modified_at' do 35 | it 'calls execute with touch and timestamp' do 36 | time = '190101010000' 37 | path = '/path/to/file' 38 | expect(instance).to receive(:execute).with("touch -mt #{time} #{path}").and_return(0) 39 | 40 | instance.modified_at(path, time) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/beaker/host/pswindows/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class PSWindowsUserTest 4 | include PSWindows::User 5 | end 6 | 7 | describe PSWindowsUserTest do 8 | let(:wmic_output) do 9 | <<~EOS 10 | Name=Administrator 11 | 12 | 13 | 14 | 15 | 16 | Name=bob foo 17 | 18 | 19 | 20 | 21 | 22 | Name=bob-dash 23 | 24 | 25 | 26 | 27 | 28 | Name=bob.foo 29 | 30 | 31 | 32 | 33 | 34 | Name=cyg_server 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | EOS 44 | end 45 | let(:command) { 'cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value' } 46 | let(:host) { double.as_null_object } 47 | let(:result) { Beaker::Result.new(host, command) } 48 | 49 | describe '#user_list' do 50 | it 'returns user names list correctly' do 51 | result.stdout = wmic_output 52 | expect(subject).to receive(:execute).with(command).and_yield(result) 53 | expect(subject.user_list).to be === ['Administrator', 'bob foo', 'bob-dash', 'bob.foo', 'cyg_server'] 54 | end 55 | 56 | it 'yields correctly with the result object' do 57 | result.stdout = wmic_output 58 | expect(subject).to receive(:execute).and_yield(result) 59 | subject.user_list do |result| 60 | expect(result.stdout).to be === wmic_output 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/beaker/host/pswindows_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module PSWindows 4 | describe Host do 5 | let(:options) { @options ? @options : {} } 6 | let(:platform) do 7 | if @platform 8 | { :platform => Beaker::Platform.new(@platform) } 9 | else 10 | { :platform => Beaker::Platform.new('windows-vers-arch-extra') } 11 | end 12 | end 13 | let(:host) do 14 | opts = options.merge(platform) 15 | opts[:is_cygwin] = false 16 | make_host('name', opts) 17 | end 18 | 19 | describe '#external_copy_base' do 20 | it 'returns previously calculated value if set' do 21 | external_copy_base_before = host.instance_variable_get(:@external_copy_base) 22 | test_value = :testn8391 23 | host.instance_variable_set(:@external_copy_base, test_value) 24 | 25 | expect(host).not_to receive(:execute) 26 | expect(host.external_copy_base).to be === test_value 27 | host.instance_variable_set(:@external_copy_base, external_copy_base_before) 28 | end 29 | 30 | it 'calls the correct command if unset' do 31 | expect(host).to receive(:execute).with(/^for\ .*ALLUSERSPROFILE.*\%\~I$/) 32 | host.external_copy_base 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/beaker/host/unix_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Unix 4 | describe Host do 5 | let(:options) { @options ? @options : {} } 6 | let(:platform) do 7 | if @platform 8 | { :platform => Beaker::Platform.new(@platform) } 9 | else 10 | { :platform => Beaker::Platform.new('el-vers-arch-extra') } 11 | end 12 | end 13 | let(:host) { make_host('name', options.merge(platform)) } 14 | let(:opts) { { :download_url => 'download_url' } } 15 | 16 | describe '#external_copy_base' do 17 | it 'returns /root in general' do 18 | copy_base = host.external_copy_base 19 | expect(copy_base).to be === '/root' 20 | end 21 | 22 | it 'returns /root if solaris but not version 10' do 23 | @platform = 'solaris-11-arch' 24 | copy_base = host.external_copy_base 25 | expect(copy_base).to be === '/root' 26 | end 27 | 28 | it 'returns / if on a solaris 10 platform' do 29 | @platform = 'solaris-10-arch' 30 | copy_base = host.external_copy_base 31 | expect(copy_base).to be === '/' 32 | end 33 | end 34 | 35 | describe '#determine_ssh_server' do 36 | it 'returns :openssh' do 37 | expect(host.determine_ssh_server).to be === :openssh 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/beaker/host/windows/group_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | describe Windows::Group do 5 | class WindowsGroupTest 6 | include Windows::Group 7 | end 8 | 9 | let(:instance) { WindowsGroupTest.new } 10 | 11 | context "Group list" do 12 | let(:result) { double(:result, :stdout => group_list_output) } 13 | let(:group_list_output) do 14 | <<~EOS 15 | Name=Foo 16 | 17 | 18 | Name=Bar6 19 | 20 | 21 | EOS 22 | end 23 | 24 | def add_group(group_name) 25 | group_list_output << <<~EOS 26 | Name=#{group_name} 27 | 28 | 29 | EOS 30 | end 31 | 32 | before do 33 | expect(instance).to receive(:execute).with(/wmic group where/).and_yield(result) 34 | end 35 | 36 | it "gets a group_list" do 37 | expect(instance.group_list).to eql(%w[Foo Bar6]) 38 | end 39 | 40 | it "gets groups with spaces" do 41 | add_group("With Spaces") 42 | expect(instance.group_list).to eql(["Foo", "Bar6", "With Spaces"]) 43 | end 44 | 45 | it "gets groups with dashes" do 46 | add_group("With-Dashes") 47 | expect(instance.group_list).to eql(%w[Foo Bar6 With-Dashes]) 48 | end 49 | 50 | it "gets groups with underscores" do 51 | add_group("With_Underscores") 52 | expect(instance.group_list).to eql(%w[Foo Bar6 With_Underscores]) 53 | end 54 | end 55 | 56 | context "Group list using powershell" do 57 | let(:group_list_using_powershell_output) do 58 | <<~EOS 59 | Foo1 60 | Bar5 61 | EOS 62 | end 63 | 64 | let(:result1) { double(:result1, :stdout => group_list_using_powershell_output) } 65 | 66 | it "gets a group_list using powershell" do 67 | expect(instance).to receive(:execute).with(/powershell.exe "Get-LocalGroup | Select-Object -ExpandProperty Name/).and_yield(result1) 68 | expect(instance.group_list_using_powershell).to eql(%w[Foo1 Bar5]) 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/beaker/host/windows/pkg_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | describe Windows::Pkg do 5 | class WindowsPkgTest 6 | include Windows::Pkg 7 | 8 | def initialize(hash, logger) 9 | @hash = hash 10 | @logger = logger 11 | end 12 | 13 | def [](k) 14 | @hash[k] 15 | end 16 | 17 | def to_s 18 | "me" 19 | end 20 | 21 | def exec 22 | # noop 23 | end 24 | end 25 | 26 | let(:opts) { @opts || {} } 27 | let(:logger) { double('logger').as_null_object } 28 | let(:instance) { WindowsPkgTest.new(opts, logger) } 29 | 30 | describe '#install_package' do 31 | before do 32 | allow(instance).to receive(:identify_windows_architecture) 33 | end 34 | 35 | context 'cygwin does not exist' do 36 | before do 37 | allow(instance).to receive(:check_for_command).and_return(false) 38 | end 39 | 40 | it 'curls the SSL URL for cygwin\'s installer' do 41 | allow(instance).to receive(:execute).with(/^setup\-x86/).ordered 42 | instance.install_package('curl') 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/beaker/host/windows/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | class WindowsUserTest 4 | include Windows::User 5 | end 6 | 7 | describe WindowsUserTest do 8 | let(:host) { double.as_null_object } 9 | let(:result) { Beaker::Result.new(host, command) } 10 | 11 | describe '#user_list' do 12 | let(:command) { 'cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value' } 13 | 14 | let(:wmic_output) do 15 | <<~EOS 16 | Name=Administrator 17 | 18 | 19 | 20 | 21 | 22 | Name=bob foo 23 | 24 | 25 | 26 | 27 | 28 | Name=bob-dash 29 | 30 | 31 | 32 | 33 | 34 | Name=bob.foo 35 | 36 | 37 | 38 | 39 | 40 | Name=cyg_server 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | EOS 50 | end 51 | 52 | it 'returns user names list correctly' do 53 | result.stdout = wmic_output 54 | expect(subject).to receive(:execute).with(command).and_yield(result) 55 | expect(subject.user_list).to be === ['Administrator', 'bob foo', 'bob-dash', 'bob.foo', 'cyg_server'] 56 | end 57 | 58 | it 'yields correctly with the result object' do 59 | result.stdout = wmic_output 60 | expect(subject).to receive(:execute).and_yield(result) 61 | subject.user_list do |result| 62 | expect(result.stdout).to be === wmic_output 63 | end 64 | end 65 | end 66 | 67 | describe "#user_list_using_powershell" do 68 | let(:command) { 'cmd /c echo "" | powershell.exe "Get-LocalUser | Select-Object -ExpandProperty Name"' } 69 | let(:user_list_using_powershell_output) do 70 | <<~EOS 71 | Administrator 72 | WDAGUtilityAccount 73 | EOS 74 | end 75 | 76 | it 'returns user names list correctly' do 77 | result.stdout = user_list_using_powershell_output 78 | expect(subject).to receive(:execute).with(command).and_yield(result) 79 | expect(subject.user_list_using_powershell).to be === ["Administrator", "WDAGUtilityAccount"] 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/beaker/options/data/badyaml.cfg: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | pe-ubuntu-lucid: 3 | roles: 4 | - agent 5 | - dashboard 6 | - database 7 | - master 8 | vmname : pe-ubuntu-lucid 9 | platform: ubuntu-10.04-i386 10 | snapshot : clean-w-keys 11 | hypervisor : fusion 12 | pe-centos6: 13 | roles: 14 | - agent 15 | vmname : pe-centos6 16 | platform: el-6-i386 17 | hypervisor : fusion 18 | snapshot *** clean-w-keys 19 | CONFIG: 20 | nfs_server: none 21 | consoleport: 443 22 | -------------------------------------------------------------------------------- /spec/beaker/options/data/hosts.cfg: -------------------------------------------------------------------------------- 1 | HOSTS: 2 | pe-ubuntu-lucid: 3 | roles: 4 | - agent 5 | - dashboard 6 | - database 7 | - master 8 | vmname : pe-ubuntu-lucid 9 | platform: ubuntu-10.04-i386 10 | snapshot : clean-w-keys 11 | hypervisor : fusion 12 | pe-centos6: 13 | roles: 14 | - agent 15 | vmname : pe-centos6 16 | platform: el-6-i386 17 | hypervisor : fusion 18 | snapshot: clean-w-keys 19 | CONFIG: 20 | nfs_server: none 21 | consoleport: 443 22 | -------------------------------------------------------------------------------- /spec/beaker/options/data/opts.txt: -------------------------------------------------------------------------------- 1 | { 2 | :debug => true, 3 | :tests => 'test.rb', 4 | :pre_suite => [ 'pre-suite.rb'], 5 | :post_suite => "post_suite1.rb,post_suite2.rb", 6 | } 7 | -------------------------------------------------------------------------------- /spec/beaker/options/options_file_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Beaker 4 | module Options 5 | describe OptionsFileParser do 6 | let(:parser) { described_class } 7 | let(:simple_opts) { File.join(__dir__, "data", "opts.txt") } 8 | 9 | it "can correctly read options from a file" do 10 | FakeFS.deactivate! 11 | expect(parser.parse_options_file(simple_opts)).to be === { :debug => true, :tests => "test.rb", :pre_suite => ["pre-suite.rb"], :post_suite => "post_suite1.rb,post_suite2.rb" } 12 | end 13 | 14 | it "raises an error on no file found" do 15 | FakeFS.deactivate! 16 | expect { parser.parse_options_file("not a valid path") }.to raise_error(ArgumentError) 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/beaker/options/options_hash_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Beaker 4 | module Options 5 | describe OptionsHash do 6 | let(:options) { described_class.new } 7 | 8 | it "supports is_pe?, defaults to pe" do 9 | expect(options).to be_is_pe 10 | end 11 | 12 | it "supports is_pe?, respects :type == foss" do 13 | options[:type] = 'foss' 14 | expect(options).not_to be_is_pe 15 | end 16 | 17 | describe '#get_type' do 18 | let(:options) { described_class.new } 19 | 20 | it 'returns pe as expected in the normal case' do 21 | newhash = options.merge({ :type => 'pe' }) 22 | expect(newhash.get_type).to be === :pe 23 | end 24 | 25 | it 'returns foss as expected in the normal case' do 26 | newhash = options.merge({ :type => 'foss' }) 27 | expect(newhash.get_type).to be === :foss 28 | end 29 | 30 | it 'returns foss as the default' do 31 | newhash = options.merge({ :type => 'git' }) 32 | expect(newhash.get_type).to be === :foss 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/beaker/options/presets_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module Beaker 4 | module Options 5 | describe Presets do 6 | let(:presets) { described_class.new } 7 | 8 | it "returns an env_vars OptionsHash" do 9 | expect(presets.env_vars).to be_instance_of(Beaker::Options::OptionsHash) 10 | end 11 | 12 | it "pulls in env vars of the form ':q_*' and adds them to the :answers of the OptionsHash" do 13 | ENV['q_puppet_cloud_install'] = 'n' 14 | env = presets.env_vars 15 | expect(env[:answers][:q_puppet_cloud_install]).to be === 'n' 16 | expect(env[:answers]['q_puppet_cloud_install']).to be === 'n' 17 | ENV.delete('q_puppet_cloud_install') 18 | end 19 | 20 | it "correctly parses the run_in_parallel array" do 21 | ENV['BEAKER_RUN_IN_PARALLEL'] = "install,configure" 22 | env = presets.env_vars 23 | expect(env[:run_in_parallel]).to eq(%w[install configure]) 24 | end 25 | 26 | it "removes all empty/nil entries in env_vars" do 27 | expect(presets.env_vars.has_value?(nil)).to be === false 28 | expect(presets.env_vars.has_value?({})).to be === false 29 | end 30 | 31 | it "returns a presets OptionsHash" do 32 | expect(presets.presets).to be_instance_of(Beaker::Options::OptionsHash) 33 | end 34 | 35 | it 'has empty host_tags' do 36 | expect(presets.presets).to have_key(:host_tags) 37 | expect(presets.presets[:host_tags]).to eq({}) 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/beaker/options/subcommand_options_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | module Options 5 | describe '#parse_subcommand_options' do 6 | let(:home_options_file_path) { ENV.fetch('HOME', nil) + '/.beaker/subcommand_options.yaml' } 7 | let(:parser_mod) { Beaker::Options::SubcommandOptionsParser } 8 | let(:parser) { parser_mod.parse_subcommand_options(argv, options_file) } 9 | let(:file_parser) { parser_mod.parse_options_file({}) } 10 | let(:argv) { [] } 11 | let(:options_file) { "" } 12 | 13 | it 'returns an empty OptionsHash if not executing a subcommand' do 14 | expect(parser).to be_a(OptionsHash) 15 | expect(parser).to be_empty 16 | end 17 | 18 | describe 'when the subcommand is init' do 19 | let(:argv) { ['init'] } 20 | 21 | it 'returns an empty OptionsHash' do 22 | expect(parser).to be_a(OptionsHash) 23 | expect(parser).to be_empty 24 | end 25 | end 26 | 27 | describe 'when the subcommand is not init' do 28 | let(:argv) { ['provision'] } 29 | let(:options_file) { home_options_file_path } 30 | 31 | it 'calls parse_options_file with subcommand options file when home_dir is false' do 32 | allow(parser_mod).to receive(:execute_subcommand?).with('provision').and_return true 33 | allow(parser_mod).to receive(:parse_options_file).with(Beaker::Subcommands::SubcommandUtil::SUBCOMMAND_OPTIONS) 34 | end 35 | 36 | it 'calls parse_options_file with home directory options file when home_dir is true' do 37 | allow(parser_mod).to receive(:execute_subcommand?).with('provision').and_return true 38 | allow(parser_mod).to receive(:parse_options_file).with(home_options_file_path) 39 | end 40 | 41 | it 'checks for file existence and loads the YAML file' do 42 | allow(File).to receive(:exist?).and_return true 43 | allow(YAML).to receive(:load_file).and_return({}) 44 | expect(file_parser).to be_a(Hash) 45 | expect(file_parser).not_to be_a(OptionsHash) 46 | end 47 | 48 | it 'returns an empty options hash when file does not exist' do 49 | allow(File).to receive(:exist?).and_return false 50 | expect(parser).to be_a(OptionsHash) 51 | expect(parser).to be_empty 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/beaker/shared/error_handler_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | module Shared 5 | describe ErrorHandler do 6 | let(:backtrace) { "I'm the backtrace\nYes I am!\nI have important information" } 7 | let(:logger) { double('logger') } 8 | 9 | before do 10 | allow(logger).to receive(:error).and_return(true) 11 | allow(logger).to receive(:pretty_backtrace).and_return(backtrace) 12 | end 13 | 14 | context 'report_and_raise' do 15 | it "records the backtrace of the exception to the logger" do 16 | ex = Exception.new("ArgumentError") 17 | allow(ex).to receive(:backtrace).and_return(backtrace) 18 | mesg = "I'm the extra message" 19 | 20 | backtrace.each_line do |line| 21 | expect(logger).to receive(:error).with(line) 22 | end 23 | 24 | expect(subject).to receive(:raise).once 25 | 26 | subject.report_and_raise(logger, ex, mesg) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/beaker/shared/options_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Beaker 4 | module Shared 5 | describe OptionsResolver do 6 | describe 'run_in_parallel?' do 7 | it 'returns true if :run_in_parallel in opts is true' do 8 | expect(subject.run_in_parallel?({ :run_in_parallel => true }, nil, nil)).to be === true 9 | end 10 | 11 | it 'returns false if :run_in_parallel in opts is false' do 12 | expect(subject.run_in_parallel?({ :run_in_parallel => false }, nil, nil)).to be === false 13 | end 14 | 15 | it 'returns false if :run_in_parallel in opts is an empty array' do 16 | expect(subject.run_in_parallel?({ :run_in_parallel => [] }, nil, nil)).to be === false 17 | end 18 | 19 | it 'returns false if :run_in_parallel in opts is an empty array but a mode is specified in options' do 20 | expect(subject.run_in_parallel?({ :run_in_parallel => [] }, { :run_in_parallel => ['install'] }, 'install')).to be === false 21 | end 22 | 23 | it 'returns true if opts is nil but a matching mode is specified in options' do 24 | expect(subject.run_in_parallel?(nil, { :run_in_parallel => ['install'] }, 'install')).to be === true 25 | end 26 | 27 | it 'returns false if opts is nil and a non matching mode is specified in options' do 28 | expect(subject.run_in_parallel?(nil, { :run_in_parallel => ['configure'] }, 'install')).to be === false 29 | end 30 | 31 | it 'returns true if opts is nil and a matching mode and a non matching mode is specified in options' do 32 | expect(subject.run_in_parallel?(nil, { :run_in_parallel => %w[configure install] }, 'install')).to be === true 33 | end 34 | 35 | it 'returns false if opts is nil and no mode is specified in options' do 36 | expect(subject.run_in_parallel?(nil, { :run_in_parallel => [] }, 'install')).to be === false 37 | end 38 | 39 | it 'returns false if opts is false but a matching mode is specified in options' do 40 | expect(subject.run_in_parallel?({ :run_in_parallel => false }, { :run_in_parallel => ['install'] }, 'install')).to be === false 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define :execute_commands_matching do |pattern| 2 | match do |actual| 3 | raise(RuntimeError, "Expected #{actual} to be a FakeHost") unless actual.is_a?(FakeHost::MockedExec) 4 | 5 | @found_count = actual.command_strings.grep(pattern).size 6 | @times.nil? ? 7 | @found_count > 0 : 8 | @found_count == @times 9 | end 10 | 11 | chain :exactly do |times| 12 | @times = times 13 | end 14 | 15 | chain :times do 16 | # clarity only 17 | end 18 | 19 | chain :once do 20 | @times = 1 21 | end 22 | 23 | def message(actual, pattern, times, found_count) 24 | msg = times == 1 ? 25 | "#{pattern} once" : 26 | "#{pattern} #{times} times" 27 | msg += " but instead found a count of #{found_count}" if found_count != times 28 | msg + " in:\n #{actual.command_strings.pretty_inspect}" 29 | end 30 | 31 | failure_message do |actual| 32 | "Expected to find #{message(actual, pattern, @times, @found_count)}" 33 | end 34 | 35 | failure_message_when_negated do |actual| 36 | "Unexpectedly found #{message(actual, pattern, @times, @found_count)}" 37 | end 38 | end 39 | 40 | RSpec::Matchers.define :execute_commands_matching_in_order do |*patterns| 41 | match do |actual| 42 | raise(RuntimeError, "Expected #{actual} to be a FakeHost") unless actual.is_a?(FakeHost::MockedExec) 43 | 44 | remaining_patterns = patterns.clone 45 | actual.command_strings.each do |line| 46 | if remaining_patterns.empty? 47 | break 48 | elsif remaining_patterns.first.match(line) 49 | remaining_patterns.shift 50 | end 51 | end 52 | 53 | remaining_patterns.empty? 54 | end 55 | 56 | def message(actual, patterns) 57 | "#{patterns.join(', ')} in order" + 58 | " in:\n #{actual.command_strings.pretty_inspect}" 59 | end 60 | 61 | failure_message do |actual| 62 | "Expected to find #{message(actual, patterns)}" 63 | end 64 | 65 | failure_message_when_negated do |actual| 66 | "Unexpectedly found #{message(actual, patterns)}" 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/mocks.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/mocks' 2 | 3 | module MockNet 4 | class HTTP 5 | class Response 6 | class ResponseHash 7 | def []key 8 | if key == "domain" 9 | nil 10 | else 11 | { 'ok' => true, 'hostname' => 'pool' } 12 | end 13 | end 14 | end 15 | 16 | def body 17 | ResponseHash.new 18 | end 19 | end 20 | 21 | class Post 22 | def initialize uri 23 | @uri = uri 24 | end 25 | 26 | def body= *_args 27 | hash 28 | end 29 | end 30 | 31 | class Put 32 | def initialize uri 33 | @uri = uri 34 | end 35 | 36 | def body= *_args 37 | hash 38 | end 39 | end 40 | 41 | class Delete 42 | def initialize uri 43 | @uri = uri 44 | end 45 | end 46 | 47 | def initialize host, port 48 | @host = host 49 | @port = port 50 | end 51 | 52 | def request _req 53 | Response.new 54 | end 55 | end 56 | end 57 | 58 | module FakeHost 59 | include RSpec::Mocks::TestDouble 60 | 61 | def self.create(name = 'fakevm', platform = 'redhat-version-arch', options = {}) 62 | options_hash = Beaker::Options::OptionsHash.new.merge(options) 63 | options_hash[:logger] = RSpec::Mocks::Double.new('logger').as_null_object 64 | host = Beaker::Host.create(name, { 'platform' => Beaker::Platform.new(platform) }, options_hash) 65 | host.extend(MockedExec) 66 | host 67 | end 68 | 69 | module MockedExec 70 | def self.extended(other) 71 | other.instance_eval do 72 | send(:instance_variable_set, :@commands, []) 73 | end 74 | end 75 | 76 | attr_accessor :commands 77 | 78 | def port_open?(_port) 79 | true 80 | end 81 | 82 | def any_exec_result 83 | RSpec::Mocks::Double.new('exec-result').as_null_object 84 | end 85 | 86 | def exec(command, _options = {}) 87 | commands << command 88 | any_exec_result 89 | end 90 | 91 | def command_strings 92 | commands.map { |c| [c.command, c.args].join(' ') } 93 | end 94 | end 95 | 96 | def log_prefix 97 | "FakeHost" 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'beaker' 2 | require 'fakefs/spec_helpers' 3 | require 'mocks' 4 | require 'helpers' 5 | require 'matchers' 6 | 7 | RSpec.configure do |config| 8 | config.include FakeFS::SpecHelpers 9 | config.include TestFileHelpers 10 | config.include HostHelpers 11 | end 12 | --------------------------------------------------------------------------------