pax_global_header00006660000000000000000000000064140760357570014530gustar00rootroot0000000000000052 comment=7810d30e434dfc9948a71b4ce8b103055ef07c58 beaker-4.30.0/000077500000000000000000000000001407603575700130455ustar00rootroot00000000000000beaker-4.30.0/.github/000077500000000000000000000000001407603575700144055ustar00rootroot00000000000000beaker-4.30.0/.github/dependabot.yml000066400000000000000000000002231407603575700172320ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: bundler directory: "/" schedule: interval: daily time: "13:00" open-pull-requests-limit: 10 beaker-4.30.0/.github/workflows/000077500000000000000000000000001407603575700164425ustar00rootroot00000000000000beaker-4.30.0/.github/workflows/release.yml000066400000000000000000000015661407603575700206150ustar00rootroot00000000000000name: Release on: create: ref_type: tag jobs: release: runs-on: ubuntu-latest if: github.repository_owner == 'voxpupuli' steps: - uses: actions/checkout@v2 - name: Install Ruby 3.0 uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' env: BUNDLE_WITHOUT: release - name: Build gem run: gem build *.gemspec - name: Publish gem to rubygems.org run: gem push *.gem env: GEM_HOST_API_KEY: '${{ secrets.RUBYGEMS_AUTH_TOKEN }}' - name: Setup GitHub packages access run: | mkdir -p ~/.gem echo ":github: Bearer ${{ secrets.GITHUB_TOKEN }}" >> ~/.gem/credentials chmod 0600 ~/.gem/credentials - name: Publish gem to GitHub packages run: gem push --key github --host https://rubygems.pkg.github.com/voxpupuli *.gem beaker-4.30.0/.github/workflows/test.yml000066400000000000000000000012121407603575700201400ustar00rootroot00000000000000name: Test on: - pull_request - push env: BUNDLE_WITHOUT: release jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - ruby: "2.4" - ruby: "2.5" - ruby: "2.6" - ruby: "2.7" - ruby: "3.0" coverage: "yes" env: COVERAGE: ${{ matrix.coverage }} steps: - uses: actions/checkout@v2 - name: Install Ruby ${{ matrix.ruby }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Run tests run: bundle exec rake spec beaker-4.30.0/.gitignore000066400000000000000000000011471407603575700150400ustar00rootroot00000000000000!.gitignore # Gem structure junit acceptance-tests pkg Gemfile.lock # Beaker-generated & local testing test.cfg options.rb merged_options.rb tmp/ log/ sut-files.tgz # YARD .yardoc yard_docs doc # Dependencies # NOTE: For consistency, please use `vendor/bundle` for Bundler-installed dependencies .bundle .vendor _vendor vendor # Byebug .byebugrc .byebug_history # SimpleCov coverage # # Editor/IDE Clutter # # (TODO: consider removing these; use local personal .gitignore instead) # Vim *.swp # JetBrains IDEA *.iml .idea/ # rbenv file .ruby-version .ruby-gemset # Vagrant folder .vagrant/ .vagrant_files/ beaker-4.30.0/.rspec000066400000000000000000000000611407603575700141570ustar00rootroot00000000000000--format documentation --color --tty --fail-fast beaker-4.30.0/.simplecov000066400000000000000000000005641407603575700150540ustar00rootroot00000000000000SimpleCov.configure do add_filter 'spec/' add_filter 'vendor/' add_filter do |file| file.lines_of_code < 10 end add_group 'Answers', '/answers/' add_group 'DSL', '/dsl/' add_group 'Host', '/host/' add_group 'Hypervisors', '/hypervisor/' add_group 'Options', '/options/' add_group 'Shared', '/shared/' end SimpleCov.start if ENV['BEAKER_COVERAGE'] beaker-4.30.0/CHANGELOG.md000066400000000000000000000374241407603575700146700ustar00rootroot00000000000000# Changelog ## [4.30.0](https://github.com/voxpupuli/beaker/compare/4.29.1...4.30.0) (2021-07-21) ### Fixed - Fix Platform version string comparison for install_local_package ([#1712](https://github.com/voxpupuli/beaker/pull/1712)) ### Added - Add initial opensuse support ([#1697](https://github.com/voxpupuli/beaker/pull/1697)) - Implement codecov reporting ([#1710](https://github.com/voxpupuli/beaker/pull/1710)) ### Changed - beaker-abs: allow latest releases ([#1706](https://github.com/voxpupuli/beaker/pull/1706)) - Align release setup with other gems ([#1707](https://github.com/voxpupuli/beaker/pull/1707)) ## [4.29.1](https://github.com/voxpupuli/beaker/tree/4.29.1) (2021-05-26) ### Fixed - Fixed `vagrant*` matching in the unix `get_ip()` # [4.29.0](https://github.com/voxpupuli/beaker/compare/4.28.1...4.29.0) - 19-05-2021 ### Added - Ruby 3.0 support # [4.28.1](https://github.com/voxpupuli/beaker/compare/4.28.0...4.28.1) - 03-10-2021 ### Fixed - Updated the ssh_preference example - Fixed various spec tests - Updated the `which` command to try `type -P` before falling back to `which` for systems that may not have `which` installed # [4.28.0](https://github.com/voxpupuli/beaker/compare/4.27.1...4.28.0) - 12-21-2020 ### Changed - Arch Linux: Update box before installing packages ([#1688](https://github.com/voxpupuli/beaker/pull/1688)) - Move the entire workflow to Github Actions ([#1678](https://github.com/voxpupuli/beaker/pull/1678)) - Allow fakefs dependency in version >= 1 < 2 ([#1687](https://github.com/voxpupuli/beaker/pull/1687)) ### Fixed - Fix License text and SPDX code ([#1681](https://github.com/voxpupuli/beaker/pull/1678)) # [4.27.1](https://github.com/voxpupuli/beaker/compare/4.27.0...4.27.1) - 09-29-2020 ### Changed - Update net-scp requirement from "~> 1.2" to ">= 1.2, < 4.0" ### Fixed - Handle systems going back in time after reboot - Enhanced error handling during the reboot sequence - Fixed time check logic during reboot - Wrap paths around "" on pswindows # [4.27.0](https://github.com/voxpupuli/beaker/compare/4.26.0...4.27.0) - 07-24-2020 ### Changed - Updated dependency versions and minimum Ruby version in gemspec to Ruby 2.4, which is the minimum version Beaker will run with. - Added Travis unit testing and disabled Jenkins integrations in preparation for transferring the repo to Vox Pupuli # [4.26.0](https://github.com/voxpupuli/beaker/compare/4.25.0...4.26.0) ### Changed - Fixed deprecated SSH option handling for `verify_ssh_key` being passed into Net::SSH. #1655 ### Removed - Removed deprecated use of `paranoid` flag with Net::SSH. #1655 # [4.25.0](https://github.com/voxpupuli/beaker/compare/4.24.0...4.25.0) ### Added - Execution of Beaker directly through ruby on localhost #1637 ([#1637](https://github.com/voxpupuli/beaker/pull/1637)) ### Fixed - Reliability improvements to the `Host#reboot` method ([#1656](https://github.com/voxpupuli/beaker/pull/1656)) ([#1659](https://github.com/voxpupuli/beaker/pull/1659)) # [4.24.0](https://github.com/voxpupuli/beaker/compare/4.23.0...4.24.0) - 2020-06-05 ### Added - Host method which ([#1651](https://github.com/voxpupuli/beaker/pull/1651)) ### Fixed - Fixed implementation for cat and file_exists? host methods for PSWindows ([#1654](https://github.com/voxpupuli/beaker/pull/1654)) - Fixed implementation for mkdir_p host method for PSWindows ([#1657](https://github.com/voxpupuli/beaker/pull/1657)) # [4.23.2](https://github.com/voxpupuli/beaker/compare/4.23.1...4.23.2) ### Fixed - Fixed Beaker's behavior when the `strict_host_key_checking` option is provided in the SSH config and Net-SSH > 5 is specified. (#1652) # [4.23.1](https://github.com/voxpupuli/beaker/compare/4.23.0...4.23.1) ### Changed/Removed - Reversed the quoting changes on Unix from #1644 in favor of only quoting on Windows. (#1650) # [4.23.0](https://github.com/voxpupuli/beaker/compare/4.22.1...4.23.0) ### Added - Relaxed dependency on `net-ssh` to `>= 5` to support newer versions. (#1648) - `cat` DSL method added. Works on both Unix and Windows hosts. (#1645) ### Changed - The `mkdir_p` and `mv` commands now double quote their file arguments. (#1644) If you rely on file globbing in these methods or elsewhere, please open an issue on the BEAKER project. - Change `reboot` method to use `who -b` for uptime detection (#1643) ### Fixed - Use Base64 UTF-16LE encoding for commands (#1626) - Fix `tmpdir` method for Powershell on Windows (#1645) # [4.22.1](https://github.com/voxpupuli/beaker/compare/4.22.0...4.22.1) ### Fixed - Removed single quotes around paths for file operation commands on `Host` https://github.com/voxpupuli/beaker/pull/1642 # [4.22.0](https://github.com/voxpupuli/beaker/compare/4.21.0...4.22.0) - 2020-05-08 ### Added - Host methods chmod and modified_at. ([#1638](https://github.com/voxpupuli/beaker/pull/1638)) ### Removed - Support for EL-5. ([#1639](https://github.com/voxpupuli/beaker/pull/1639)) ([#1640](https://github.com/voxpupuli/beaker/pull/1640)) # [4.21.0](https://github.com/voxpupuli/beaker/compare/4.20.0...4.21.0) - 2020-03-31 ### Added - Empty file `/etc/environment` while preparing ssh environment on Ubuntu 20.04 to keep the current behavior and consider all variables from `~/.ssh/environment`. ([#1635](https://github.com/voxpupuli/beaker/pull/1635)) # [4.20.0](https://github.com/voxpupuli/beaker/compare/4.19.0...4.20.0) - 2020-03-19 ### Added - Vagrant RSync/SSH settings will now be picked up if set via beaker-vagrant ([#1634](https://github.com/voxpupuli/beaker/pull/1634) and [beaker-vagrant#28](https://github.com/voxpupuli/beaker-vagrant/pull/28)) # [4.19.0](https://github.com/voxpupuli/beaker/compare/4.18.0...4.19.0) - 2020-03-13 ### Added - `apt-transport-https` package will now be installed on Debian-based systems as part of the prebuilt process. ([#1631](https://github.com/voxpupuli/beaker/pull/1631)) - Ubuntu 19.10 and 20.04 code name handling. ([#1632](https://github.com/voxpupuli/beaker/pull/1632)) ### Changed - The `wait_time`, `max_connection_tries`, and `uptime_retries` parameters have been added to `Host::Unix::Exec.reboot`. This allows for more fine-grained control over how the reboot is handled. ([#1625](https://github.com/voxpupuli/beaker/pull/1625)) ### Fixed - In `hosts.yml`, `packaging_platform` will now default to `platform` if unspecified. This fixed a bug where beaker would fail unless you specified both values in your config, even if both values were identical. ([#1628](https://github.com/voxpupuli/beaker/pull/1628)) - `version_is_less` will now correctly handle builds and RCs when used in version numbers. ([#1630](https://github.com/voxpupuli/beaker/pull/1630)) ### Security - Update `rake` to `~> 12.0`, which currently resolves to `12.3.3` to remediate [CVE-2020-8130](https://nvd.nist.gov/vuln/detail/CVE-2020-8130) # [4.18.0](https://github.com/voxpupuli/beaker/compare/4.17.0...4.18.0) - 2020-02-26 ### Changed - Thor dependency bumped to >=1.0.1 <2.0 # [4.17.0](https://github.com/voxpupuli/beaker/compare/4.16.0...4.17.0) - 2020-02-20 ### Added - Windows support in `host_helpers` ([#1622](https://github.com/voxpupuli/beaker/pull/1622)) - EL 8 support ([#1623](https://github.com/voxpupuli/beaker/pull/1623)) # [4.16.0](https://github.com/voxpupuli/beaker/compare/4.15.0...4.16.0) - 2020-02-05 ### Added - release section to README ([#1618](https://github.com/voxpupuli/beaker/pull/1618)) - false return if `link_exists?` raises an error ([#1613](https://github.com/voxpupuli/beaker/pull/1613)) ### Fixed - `host.reboot` uses `uptime` rather than `ping` to check host status ([#1619](https://github.com/voxpupuli/beaker/pull/1619)) # [4.15.0](https://github.com/voxpupuli/beaker/compare/4.14.1...4.15.0) - 2020-01-30 ### Added - macOS 10.15 Catalina support (BKR-1621) # [4.14.1](https://github.com/voxpupuli/beaker/compare/4.14.0...4.14.1) - 2019-11-18 ### Fixed - `fips_mode?` detection (#1607) # [4.14.0](https://github.com/voxpupuli/beaker/compare/4.13.1...4.14.0) - 2019-11-12 ### Added - Pre-built steps output stacktraces when aborted (QENG-7466) # [4.13.1](https://github.com/voxpupuli/beaker/compare/4.13.0...4.13.1) - 2019-10-07 ### Fixed - Use correct platform variant for FIPS repo configs download (BKR-1616) # [4.13.0](https://github.com/voxpupuli/beaker/compare/4.12.0...4.13.0) - 2019-09-16 ### Added - Host `enable_remote_rsyslog` method (QENG-7466) # [4.12.0](https://github.com/voxpupuli/beaker/compare/4.11.1...4.12.0) - 2019-08-14 ### Added - redhatfips as a recognized platform (PE-27037) # [4.11.1](https://github.com/voxpupuli/beaker/compare/4.11.0...4.11.1) - 2019-08-13 ### Changed - `host.down?`'s wait from a fibonacci to a constant wait (BKR-1595) # [4.11.0](https://github.com/voxpupuli/beaker/compare/4.10.0...4.11.0) - 2019-07-22 ### Added - FIPS detection host method (BKR-1604) - PassTest exception catching for standard reporting # [4.10.0](https://github.com/voxpupuli/beaker/compare/4.9.0...4.10.0) - 2019-07-01 ### Added - Down & Up Checking to Host#reboot (BKR-1595) # [4.9.0](https://github.com/voxpupuli/beaker/compare/4.8.0...4.9.0) - 2019-06-19 ### Changed - SSH Connection failure backoff shortened (BKR-1599) # [4.8.0](https://github.com/voxpupuli/beaker/compare/4.7.0...4.8.0) - 2019-04-17 ### Added - Support for Fedora >= 30 (BKR-1589) - Codenames for Ubuntu 18.10, 19.04, and 19.10 ### Changed - Remove "repos-pe" prefix for repo filenames # [4.7.0](https://github.com/voxpupuli/beaker/compare/4.6.0...4.7.0) - 2019-04-17 ### Added - Provide for OpenSSL 1.1.x+ support - enable Solaris10Sparc pkgutil SSL CA2 (IMAGES-844) ### Changed - update pry-byebug dependency 3.4.2->3.6 (BKR-1568) - disabling hostkey checks for cisco hosts (QENG-7108) - Change behavior of ruby versioning to accept job-parameter RUBY\_VER - Change subcommand pre-suite to install ruby 2.3.1 # [4.6.0](https://github.com/voxpupuli/beaker/compare/4.5.0...4.6.0) - 2019.03.07 ### Added - Codename for Debian 10 'Buster' # [4.5.0](https://github.com/voxpupuli/beaker/compare/4.4.0...4.5.0) - 2019.01.23 ### Changed - Do not mirror profile.d on Debian (BKR-1559) # [4.4.0](https://github.com/voxpupuli/beaker/compare/4.3.0...4.4.0) - 2019.01.09 ### Added - Return root considerations for appending on nexus devices (BKR-1562) - Permit user environment on osx-10.14 (BKR-1534) - Add host helpers for working with files (BKR-1560) ### Changed - Replace ntpdate with crony on RHEL-8 (BKR-1555) # [4.3.0](https://github.com/voxpupuli/beaker/compare/4.2.0...4.3.0) - 2018.12.12 ### Added - Use zypper to install RPM packages on SLES (PA-2336) - Add only-fails capability to beaker (BKR-1523) # [4.2.0](https://github.com/voxpupuli/beaker/compare/4.1.0...4.2.0) - 2018.11.28 ### Added - `BEAKER_HYPERVISOR` environment variable to choose the beaker-hostgenerator hypervisor ### Changed - Handling of vsh appended commands for cisco_nexus (BKR-1556) - Acceptance tests: Add backoffs to other create_remote_file test ### Fixed - Don't always start a new container with docker (can be reused between invocations of the provision and exec beaker subcommands) (BKR-1547) - Recursively remove unpersisted subcommand options (BKR-1549) # [4.1.0](https://github.com/voxpupuli/beaker/compare/4.0.0...4.1.0) - 2018.10.25 ### Added - `--preserve-state` flag will preserve a given host options hash across subcommand runs(BKR-1541) ### Changed - Added additional tests for EL-like systems and added 'redhat' support where necessary - Test if puppet module is installed in '/' and avoid stripping of path seperator # [4.0.0](https://github.com/voxpupuli/beaker/compare/3.37.0...4.0.0) - 2018-08-06 ### Fixed - `host.rsync_to` throws `Beaker::Host::CommandFailure` if rsync call fails (BKR-463) - `host.rsync_to` throws `Beaker::Host::CommandFailure` if rsync does not exist on remote system (BKR-462) - `host.rsync_to` now check through configured SSH keys to use the first valid one - Updated some `Beaker::Host` methods to always return a `Result` object ### Added - Adds `Beaker::Host#chown`, `#chgrp`, and `#ls_ld` methods (BKR-1499) - `#uninstall_package` host helper, to match `#install_package` - `Host.uninstall_package` for FreeBSD - Now easily check a command's exit status by calling `Result.success?()` for a simple, truthy result. No need to validate the exit code manually. ### Changed - `#set_env` no longer calls `#configure_type_defaults_on` - `beaker-puppet` DSL Extension Library has been formally split into a standard DSL Extension Library and removed as a dependency from Beaker. Please see our [upgrade guidelines](docs/how_to/upgrade_from_3_to_4.md). - Beaker's Hypervisor Libraries have been removed as dependencies. Please see our [upgrade guidelines](docs/how_to/upgrade_from_3_to_4.md). ### Removed - `PEDefaults` has been moved to `beaker-pe` # [3.37.0](https://github.com/voxpupuli/beaker/compare/3.36.0...3.37.0) - 2018-07-11 ### Fixed - Exit early on --help/--version/--parse-only arguments instead of partial dry-run ### Added - `Beaker::Shared::FogCredentials.get_fog_credentials()` to parse .fog credential files ### Changed - `beaker-pe` is no longer automagically included. See [the upgrade guide](/docs/how_to/upgrade_from_3_to_4.md}) for more info - `beaker-puppet` is no longer required as a dependency # [3.36.0](https://github.com/voxpupuli/beaker/compare/3.35.0...3.36.0) - 2018-06-18 ### Fixed - Raise `ArgumentError` when passing `role = nil` to `only_host_with_role()` or `find_at_most_one_host_with_role()` - Use `install_package_with_rpm` in `add_el_extras` ### Added - Installation instructions for contributors - Markdown formatting guidelines for `docs/` - Glossary for project jargon in [`docs/concepts/glossary.md`](docs/concepts/glossary.md) - Use AIX 6.1 packages everywhere for puppet6 # [3.35.0](https://github.com/voxpupuli/beaker/compare/3.34.0...3.35.0) - 2018-05-16 ### Fixed - Report accurate location of generated smoke test - Accept comma-separated tests for exec subcommand ### Added - Added optional ability to use ERB in nodeset YAML files # [3.34.0](https://github.com/voxpupuli/beaker/compare/3.33.0...3.34.0) - 2018-03-26 ### Fixed - Recursively glob the tests directory ### Added - Codename for Ubuntu 18.04 'Bionic' # [3.33.0](https://github.com/voxpupuli/beaker/compare/3.32.0...3.33.0) - 2018-03-07 ### Changed - Use relative paths for beaker exec # [3.32.0](https://github.com/voxpupuli/beaker/compare/3.31.0...3.32.0) - 2018-02-22 ### Changed - Fully qualify sles ssh restart cmd - Deprecated deploy_package_repo methods - Configuration of host type in host_prebuilt_steps ### Added - Added missing beaker options for subcommand passthorugh # [3.31.0](https://github.com/voxpupuli/beaker/compare/3.30.0...3.31.0) - 2018-01-22 ### Changed - Clean up ssh paranoid setting deprecation warnings ### Added - Add macOS 10.13 support # [3.30.0](https://github.com/voxpupuli/beaker/compare/3.29.0...3.30.0) - 2018-01-10 ### Changed - Use `host.hostname` when combining options host_hash with host instance options ### Removed - `amazon` as a platform value ### Added - Load project options from .beaker.yml # [3.29.0](https://github.com/voxpupuli/beaker/compare/3.28.0...3.29.0) - 2017-11-16 ### Added - Adding default to read fog credentials # [3.28.0](https://github.com/voxpupuli/beaker/compare/3.27.0...3.28.0) - 2017-11-01 ### Fixed - corruption of `opts[:ignore]` when using `rsync` # [3.27.0](https://github.com/voxpupuli/beaker/compare/3.26.0...3.27.0) - 2017-10-19 ### Added - support amazon as a platform - add codenames for MacOS 10.13 and Ubuntu Artful # [3.26.0](https://github.com/voxpupuli/beaker/compare/3.25.0...3.26.0) - 2017-10-05 ### Added - concept of `manual_test` and `manual_step` # [3.25.0](https://github.com/voxpupuli/beaker/compare/3.24.0...3.25.0) - 2017-09-26 \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* beaker-4.30.0/CODEOWNERS000066400000000000000000000000001407603575700144260ustar00rootroot00000000000000beaker-4.30.0/CONTRIBUTING.md000066400000000000000000000223441407603575700153030ustar00rootroot00000000000000# How To Contribute To Beaker Contributions are welcomed. Simple bug fixes and minor enhancements will usually be accepted. Larger features should be discussed with a team member before you invest in developing them with the expectation that they will be merged. ## Getting Started Beaker does not use GitHub Issues, but an internal ticketing system running Jira that interfaces with other services. To be accepted by the maintainers, changes must follow this workflow and tagging scheme. See [ticket process doc](docs/concepts/ticket_process.md) for a * Create a [Jira account](http://tickets.puppetlabs.com). * Make sure you have a [GitHub account](https://github.com/signup/free). * Submit a ticket for your issue on Jira, assuming one does not already exist. * Clearly describe the issue including steps to reproduce when it is a bug. * File the ticket in the [BKR project](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20BKR). * Fork the [Beaker repository on GitHub](https://github.com/puppetlabs/beaker). * [Get Beaker set up for development](docs/tutorials/installation.md#for-development). ## Making Changes Contributions are accepted in the form of pull requests against the master branch on GitHub. * Create a topic branch on your fork of [puppetlabs/beaker](https://github.com/puppetlabs/beaker) based on `master`. * Make commits of logical units. If your commits are a mess, you will be asked to [rebase or at least squash](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) your PR. * Check for unnecessary whitespace with `git diff --check` before committing. * Make sure your commit messages are in the proper format: ``` (BKR-1234) Make the example in CONTRIBUTING imperative and concrete Without this patch applied the example commit message in the CONTRIBUTING document is not a concrete example. This is a problem because the contributor is left to imagine what the commit message should look like based on a description rather than an example. This patch fixes the problem by making the example concrete and imperative. The first line is a real life imperative statement with a ticket number from our issue tracker. The body describes the behavior without the patch, why this is a problem, and how the patch fixes the problem when applied. ``` * During the time that you are working on your patch the master Beaker branch may have changed - be sure to [rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) on top of [Beaker's](https://github.com/puppetlabs/beaker) master branch before you submit your PR. A successful rebase ensures that your PR will merge cleanly. * When you're ready for review, create a new pull request. #### PR Requirements Pull Requests are subject to the following requirements: * Commits must be logical units. Follow these [basic guidelines](https://github.com/trein/dev-best-practices/wiki/Git-Commit-Best-Practices#basic-rules), and don't be afraid to make too many commits: it's always easier to squash than to fixup. * Must not contain changes unrelated to the ticket being worked on. Issues you encounter as directly related to the main work for a ticket are fiar game. Many beaker components only get infrequent updates so it is not uncommon to encounter dependency version changes that cause problems. These can be addressed with a `(MAINT)` commit within the feature PR you're working on. Larger or only peripherally related changes should go through their own ticket, which you can create; tickets with attached PRs are generally accepted. * Must merge cleanly. Only fast-forward merges are accepted, so make sure the PR shows as a clean merge. * On that note, merge commits are not accepted. In order to keep your feature branch up-to-date and ensure a clean merge, you should [rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) on top of beaker's master. You can also use this opportunity to keep your fork up to date. That workflow looks like this: ~~~console you@local:beaker $ git checkout master Switched to branch 'master' Your branch is up to date with 'origin/master'. you@local:beaker $ git fetch upstream you@local:beaker $ git merge upstream/master Updating a01b5732..a565e1ac Fast-forward lib/beaker/logger.rb | 2 +- spec/beaker/logger_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) you@local:beaker $ git push Total 0 (delta 0), reused 0 (delta 0) To https://github.com/Dakta/beaker.git a01b5732..a565e1ac master -> master you@local:beaker $ git checkout BKR-816 Switched to branch 'BKR-816' you@local:beaker $ git rebase master # if you have conflicts, they'll appear here. Manually fix the listed files then use `git rebase --continue`. Repeat as necessary for each conflicting commit. First, rewinding head to replay your work on top of it... Fast-forwarded BKR-816 to master. you@local:beaker $ git push --set-upstream origin BKR-816 Counting objects: 9, done. Delta compression using up to 8 threads. Compressing objects: 100% (9/9), done. Writing objects: 100% (9/9), 2.05 KiB | 2.05 MiB/s, done. Total 9 (delta 6), reused 0 (delta 0) remote: Resolving deltas: 100% (6/6), completed with 6 local objects. To https://github.com/Dakta/beaker.git + [new branch] BKR-816 -> BKR-816 Branch 'BKR-816' set up to track remote branch 'BKR-816' from 'origin'. ~~~ #### Courtesy Please do not introduce personal ignores into the `.gitignore`, such as IDE configurations, editor version files, or personal testing artefacts. You may find it valuable to add the first two to [a global ignore](https://help.github.com/articles/ignoring-files/#create-a-global-gitignore), and the third to [a repository-level ignore](https://help.github.com/articles/ignoring-files/#explicit-repository-excludes). ### Testing Submitted PR's will be tested in a series of spec and acceptance level tests - the results of these tests will be evaluated by a Beaker team member, as acceptance test results are not accessible by the public. Testing failures that require code changes will be communicated in the PR discussion. * Make sure you have added [RSpec](http://rspec.info/) tests that exercise your new code. These test should be located in the appropriate `beaker/spec/` subdirectory. The addition of new methods/classes or the addition of code paths to existing methods/classes requires additional RSpec coverage. * Beaker uses RSpec 3.1.0+, and you should **NOT USE** deprecated `should`/`stub` methods - **USE** `expect`/`allow`. See a nice blog post from 2013 on [RSpec's new message expectation syntax](http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/). * Run the tests to assure nothing else was accidentally broken, using `rake test` * **Bonus**: if possible ensure that `rake test` runs without failures for additional Rubies (versions 1.9.3 and above). ### Documentation * Add an entry in the [CHANGELOG.md](CHANGELOG.md). Refer to the CHANGELOG itself for message style/form details. * Make sure that you have added documentation using [YARD](http://yardoc.org/) as necessary for any new code introduced. See [DOCUMENTING](DOCUMENTING.md). * More user friendly documentation will be required for PRs unless exempted. Documentation lives in the [docs/ folder](docs). ## Making Changes Without a Ticket The following kinds of changes are made without a corresponding Jira ticket. ### Maintenance For changes of a trivial nature, it is not always necessary to create a new ticket in Jira. In this case, it is appropriate to start the first line of a commit with `(MAINT)` instead of a ticket/issue number. ```` (MAINT) Fix whitespace - remove additional spaces that appear at EOL ```` ### Version Bump For Gem Release To prepare for a new gem release of Beaker the `version.rb` file is updated with the upcoming gem version number. This is submitted with `(GEM)` instead of a ticket/issue number. ```` (GEM) Update version for Beaker 1.16.1 ```` ### History File Update To prepare for a new gem release of Beaker (after the version has been bumped) the `HISTORY.md` file is updated with the latest GitHub log. This is submitted with `(HISTORY)` instead of a ticket/issue number. ```` (HISTORY) Update history for release of Beaker 1.16.1 ```` ## Submitting Changes * Push your changes to a topic branch in your fork of the repository. * Submit a pull request to [Beaker](https://github.com/puppetlabs/beaker) * Update your [Jira](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20BKR) ticket to mark that you have submitted code and are ready for it to be considered for merge (Status: Ready for Merge). PRs are reviewed as time permits. # Additional Resources * [Beaker Glossary](docs/concepts/glossary.md) * [Puppet community guidelines](https://docs.puppet.com/community/community_guidelines.html) * [Bug tracker (Jira)](http://tickets.puppetlabs.com) * [BKR Jira Project](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20BKR) * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) * Questions? Comments? Contact the Beaker team at the #puppet-dev IRC channel on freenode.org * The keyword `beaker` is monitored and we'll get back to you as quick as we can. beaker-4.30.0/DOCUMENTING.md000066400000000000000000000154601407603575700151510ustar00rootroot00000000000000 # Contributing Documentation to Beaker ## Documents Beyond the usual README, Beaker has an ever-growing amount of written documentation including tutorials, recipes, and other helpful information (see [docs/](docs)). Changes larger than bug fixes will usually be required to include such user-friendly documentation. ## Style User-friendly Documentation (tutorials, how-tos, etc.) uses [GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/). We adopt the following conventions. ### Text - Don't hard-wrap text blocks (including lists). There is disagreement about the ideal editor width, so don't hard-wrap text. Code blocks may be wrapped as appropriate. ### Lists - Use `-` lists to avoid confusion with `*emphasis*`. - Be sure to indent code blocks within list items. ### Headers - Use Markdown Headers. - Always place a blank line before and after headers. ### Code - Use inline code for snippets (paths, file names, variables, partial commands, etc.) - Use fenced blocks for code of one full line or greater - Offset fenced blocks with whitespace before and after, except in list items. - Mark the language of code blocks: ~~~ ```console $ beaker --help ``` ~~~ Supported languages are documented [here](https://github.com/github/linguist/blob/master/lib/linguist/languages.yml). You will probably be using `console`, `ruby`, `puppet`, or `yaml` most of the time. - It's nice to provide a shebang (`$`) in console examples to distinguish input from output. ## Inline All inline/generated documentation uses [YARD](http://yardoc.org/). Below is an example usage, a quick summary of documentation expectations, and finally a short reference for those new to YARD. They say a picture is worth a thousand words, hopefully this example will be worth more than the 154 it’s composed of: ```ruby # # @param [Array, Host, #execute] hosts The host(s) to act on # @param [String] action The action to perform # @param [Hash{Symbol=>String}] options The options hash # @option [Boolean] :noop (false) Set to true if you want noop mode # @option [Boolean] :verbose (true) Whether or not to log verbosely # # @yield [result] Yields the result of action for further checking # @yieldparam [Result] result A ValueObject containing action stats # @return [void] This method is a helper for remotely executing tasks # # @example Use this method when action must be sudone # sudo_with_logging( master, ‘reboot -r’, :verbose => false ) # # @example Pass this a block to perform additional checks # sudo_with_logging( master, ‘apt-get update’ ) do |result| # if result.exit_code == 1 # fail_test( ‘Apt has failed us again!’ ) # end # end # # @see TestCase#on # @api dsl # def sudo_with_logging hosts, action, options = {}, &block return if options[:noop] if hosts.is_a?( Array ) hosts.each {|h| sudo_with_logging h, action, options, &block } else result = host.execute( action, options.delete(:verbose) ) yield result if block_given? end end ``` ## Documentation Guide Most of our documentation is done with the @tag syntax. With a few execptions tags follow this format: @tag [TypeOfValueInBrackets] nameOfValue Multi-word description that can span multiple lines, as long as lines after the first have greater indentation Note: The `tag` name and the `nameOfValue` in question cannot contain spaces. All sections should be considered mandatory, but in practice a committer can walk a contributor through the process and help ensure a high quality of documentation. When contributing keep especially in mind that an `@example` block will go a long way in helping understand the use case (which also encourages use by others) and the @api tag helps to understand the scope of a Pull Request. Please be liberal with whitespace (not trailing whitespace) and vertical alignment as it helps readability while “in code”. Default indentation is two spaces unless there are readability/vertical alignment concerns. While the `@params`, `@returns`, etc... may seem redundant, they encourage thinking through exactly what you are doing and because of their strict format they allow a level of tooling not available in regular Ruby. You are encouraged to run the YARD documentation server locally by: ```console $ rake docs ``` or ```console $ rake docs:bg ``` depending on whether you want the server to run in the foreground or not Wait for the documentation to compile and then point your browser to [http://localhost:8808](http://localhost:8808). ## A Simple YARD Reference A Hash that must be in `{:symbol => ‘string’}` format: @param [Hash] my_hash This is also valid, and maybe more obvious to those used to Ruby: @param [Hash{Symbol=>String}] When specifying an options hash you use `@option` to specify keys/values: @param [Hash{Symbol=>String}] my_opts An options hash @option my_opts [ClassOfValue] :key_in_question A Description @option my_opts [Fixnum] :log_level The log level to run in. @option my_opts [Boolean] :turbo (true) Who doesn’t want turbos? This parameter takes an unordered list of Strings, Fixnums, and Floats: @param [Array] This is an ordered list of String, then Fixnum: @param [Array<(String, Fixnum)>] This is a parameter that needs to implement certain methods: @param [#[], #to_s] This documents that a method may return any of the types listed: @return [String, self, nil] This is the return statement for a method only used for side effects: @return [void] If a method returns a boolean (TrueClass or FalseClass) write: @return [Boolean] List possible classes that the method may raise: @raise [Beaker::PendingTest] List parameter names yielded by a method: @yield [result, self] And specify what kind of object is yielded: @yieldparam [Result] result An `example` block contains a tag, description and then indented code: @example Accessing Host defaults using hash syntax host[‘platform’] #=> ‘debian-6-amd64’ The `api` tag can have anything behind it, please use the following when documenting harness methods: @api dsl Part of the testing dsl used within tests @api public Methods third party integrations can rely on @api private Methods private to the harness, not to be used externally When deprecating a method include information on newer alternatives: @deprecated This method is horrible. Please use {#foo} or {#bar}. When you want to reference other information use: @see ClassOrModule @see #other_method @see http://web.url.com/reference Title for the link beaker-4.30.0/Gemfile000066400000000000000000000021511407603575700143370ustar00rootroot00000000000000source ENV['GEM_SOURCE'] || 'https://rubygems.org' gemspec # This section of the gemspec is for Puppet CI; it will pull in # a supported beaker library for testing to overwrite the gemspec if # a corresponding ENV var is found. Currently, the only supported lib # is beaker-pe, which can be injected into the dependencies when the # following ENV vars are defined: BEAKER_PE_PR_AUTHOR, # BEAKER_PE_PR_COMMIT, BEAKER_PE_PR_REPO_URL. These correspond to the # ghprb variables ghprbPullAuthorLogin, ghprbActualCommit, # and ghprbAuthorRepoGitUrl respectively. In the "future", we should # make this a standard format so we can pull in more than predefined # variables. if ENV['BEAKER_PE_PR_REPO_URL'] lib = ENV['BEAKER_PE_PR_REPO_URL'].match(/\/([^\/]+)\.git$/)[1] author = ENV['BEAKER_PE_PR_AUTHOR'] ref = ENV['BEAKER_PE_PR_COMMIT'] gem lib, :git => "git@github.com:#{author}/#{lib}.git", :branch => ref end group :release do gem 'github_changelog_generator', require: false end group :coverage, optional: ENV['COVERAGE']!='yes' do gem 'simplecov-console', :require => false gem 'codecov', :require => false end beaker-4.30.0/HISTORY.md000066400000000000000000000371531407603575700145410ustar00rootroot00000000000000## [4.30.0](https://github.com/voxpupuli/beaker/compare/4.29.1...4.30.0) (2021-07-21) ### Fixed - Fix Platform version string comparison for install_local_package ([#1712](https://github.com/voxpupuli/beaker/pull/1712)) ### Added - Add initial opensuse support ([#1697](https://github.com/voxpupuli/beaker/pull/1697)) - Implement codecov reporting ([#1710](https://github.com/voxpupuli/beaker/pull/1710)) ### Changed - beaker-abs: allow latest releases ([#1706](https://github.com/voxpupuli/beaker/pull/1706)) - Align release setup with other gems ([#1707](https://github.com/voxpupuli/beaker/pull/1707)) ## [4.29.1](https://github.com/voxpupuli/beaker/tree/4.29.1) (2021-05-26) ### Fixed - Fixed `vagrant*` matching in the unix `get_ip()` # [4.29.0](https://github.com/voxpupuli/beaker/compare/4.28.1...4.29.0) - 19-05-2021 ### Added - Ruby 3.0 support # [4.28.1](https://github.com/voxpupuli/beaker/compare/4.28.0...4.28.1) - 03-10-2021 ### Fixed - Updated the ssh_preference example - Fixed various spec tests - Updated the `which` command to try `type -P` before falling back to `which` for systems that may not have `which` installed # [4.28.0](https://github.com/voxpupuli/beaker/compare/4.27.1...4.28.0) - 12-21-2020 ### Changed - Arch Linux: Update box before installing packages ([#1688](https://github.com/voxpupuli/beaker/pull/1688)) - Move the entire workflow to Github Actions ([#1678](https://github.com/voxpupuli/beaker/pull/1678)) - Allow fakefs dependency in version >= 1 < 2 ([#1687](https://github.com/voxpupuli/beaker/pull/1687)) ### Fixed - Fix License text and SPDX code ([#1681](https://github.com/voxpupuli/beaker/pull/1678)) # [4.27.1](https://github.com/voxpupuli/beaker/compare/4.27.0...4.27.1) - 09-29-2020 ### Changed - Update net-scp requirement from "~> 1.2" to ">= 1.2, < 4.0" ### Fixed - Handle systems going back in time after reboot - Enhanced error handling during the reboot sequence - Fixed time check logic during reboot - Wrap paths around "" on pswindows # [4.27.0](https://github.com/voxpupuli/beaker/compare/4.26.0...4.27.0) - 07-24-2020 ### Changed - Updated dependency versions and minimum Ruby version in gemspec to Ruby 2.4, which is the minimum version Beaker will run with. - Added Travis unit testing and disabled Jenkins integrations in preparation for transferring the repo to Vox Pupuli # [4.26.0](https://github.com/voxpupuli/beaker/compare/4.25.0...4.26.0) ### Changed - Fixed deprecated SSH option handling for `verify_ssh_key` being passed into Net::SSH. #1655 ### Removed - Removed deprecated use of `paranoid` flag with Net::SSH. #1655 # [4.25.0](https://github.com/voxpupuli/beaker/compare/4.24.0...4.25.0) ### Added - Execution of Beaker directly through ruby on localhost #1637 ([#1637](https://github.com/voxpupuli/beaker/pull/1637)) ### Fixed - Reliability improvements to the `Host#reboot` method ([#1656](https://github.com/voxpupuli/beaker/pull/1656)) ([#1659](https://github.com/voxpupuli/beaker/pull/1659)) # [4.24.0](https://github.com/voxpupuli/beaker/compare/4.23.0...4.24.0) - 2020-06-05 ### Added - Host method which ([#1651](https://github.com/voxpupuli/beaker/pull/1651)) ### Fixed - Fixed implementation for cat and file_exists? host methods for PSWindows ([#1654](https://github.com/voxpupuli/beaker/pull/1654)) - Fixed implementation for mkdir_p host method for PSWindows ([#1657](https://github.com/voxpupuli/beaker/pull/1657)) # [4.23.2](https://github.com/voxpupuli/beaker/compare/4.23.1...4.23.2) ### Fixed - Fixed Beaker's behavior when the `strict_host_key_checking` option is provided in the SSH config and Net-SSH > 5 is specified. (#1652) # [4.23.1](https://github.com/voxpupuli/beaker/compare/4.23.0...4.23.1) ### Changed/Removed - Reversed the quoting changes on Unix from #1644 in favor of only quoting on Windows. (#1650) # [4.23.0](https://github.com/voxpupuli/beaker/compare/4.22.1...4.23.0) ### Added - Relaxed dependency on `net-ssh` to `>= 5` to support newer versions. (#1648) - `cat` DSL method added. Works on both Unix and Windows hosts. (#1645) ### Changed - The `mkdir_p` and `mv` commands now double quote their file arguments. (#1644) If you rely on file globbing in these methods or elsewhere, please open an issue on the BEAKER project. - Change `reboot` method to use `who -b` for uptime detection (#1643) ### Fixed - Use Base64 UTF-16LE encoding for commands (#1626) - Fix `tmpdir` method for Powershell on Windows (#1645) # [4.22.1](https://github.com/voxpupuli/beaker/compare/4.22.0...4.22.1) ### Fixed - Removed single quotes around paths for file operation commands on `Host` https://github.com/voxpupuli/beaker/pull/1642 # [4.22.0](https://github.com/voxpupuli/beaker/compare/4.21.0...4.22.0) - 2020-05-08 ### Added - Host methods chmod and modified_at. ([#1638](https://github.com/voxpupuli/beaker/pull/1638)) ### Removed - Support for EL-5. ([#1639](https://github.com/voxpupuli/beaker/pull/1639)) ([#1640](https://github.com/voxpupuli/beaker/pull/1640)) # [4.21.0](https://github.com/voxpupuli/beaker/compare/4.20.0...4.21.0) - 2020-03-31 ### Added - Empty file `/etc/environment` while preparing ssh environment on Ubuntu 20.04 to keep the current behavior and consider all variables from `~/.ssh/environment`. ([#1635](https://github.com/voxpupuli/beaker/pull/1635)) # [4.20.0](https://github.com/voxpupuli/beaker/compare/4.19.0...4.20.0) - 2020-03-19 ### Added - Vagrant RSync/SSH settings will now be picked up if set via beaker-vagrant ([#1634](https://github.com/voxpupuli/beaker/pull/1634) and [beaker-vagrant#28](https://github.com/voxpupuli/beaker-vagrant/pull/28)) # [4.19.0](https://github.com/voxpupuli/beaker/compare/4.18.0...4.19.0) - 2020-03-13 ### Added - `apt-transport-https` package will now be installed on Debian-based systems as part of the prebuilt process. ([#1631](https://github.com/voxpupuli/beaker/pull/1631)) - Ubuntu 19.10 and 20.04 code name handling. ([#1632](https://github.com/voxpupuli/beaker/pull/1632)) ### Changed - The `wait_time`, `max_connection_tries`, and `uptime_retries` parameters have been added to `Host::Unix::Exec.reboot`. This allows for more fine-grained control over how the reboot is handled. ([#1625](https://github.com/voxpupuli/beaker/pull/1625)) ### Fixed - In `hosts.yml`, `packaging_platform` will now default to `platform` if unspecified. This fixed a bug where beaker would fail unless you specified both values in your config, even if both values were identical. ([#1628](https://github.com/voxpupuli/beaker/pull/1628)) - `version_is_less` will now correctly handle builds and RCs when used in version numbers. ([#1630](https://github.com/voxpupuli/beaker/pull/1630)) ### Security - Update `rake` to `~> 12.0`, which currently resolves to `12.3.3` to remediate [CVE-2020-8130](https://nvd.nist.gov/vuln/detail/CVE-2020-8130) # [4.18.0](https://github.com/voxpupuli/beaker/compare/4.17.0...4.18.0) - 2020-02-26 ### Changed - Thor dependency bumped to >=1.0.1 <2.0 # [4.17.0](https://github.com/voxpupuli/beaker/compare/4.16.0...4.17.0) - 2020-02-20 ### Added - Windows support in `host_helpers` ([#1622](https://github.com/voxpupuli/beaker/pull/1622)) - EL 8 support ([#1623](https://github.com/voxpupuli/beaker/pull/1623)) # [4.16.0](https://github.com/voxpupuli/beaker/compare/4.15.0...4.16.0) - 2020-02-05 ### Added - release section to README ([#1618](https://github.com/voxpupuli/beaker/pull/1618)) - false return if `link_exists?` raises an error ([#1613](https://github.com/voxpupuli/beaker/pull/1613)) ### Fixed - `host.reboot` uses `uptime` rather than `ping` to check host status ([#1619](https://github.com/voxpupuli/beaker/pull/1619)) # [4.15.0](https://github.com/voxpupuli/beaker/compare/4.14.1...4.15.0) - 2020-01-30 ### Added - macOS 10.15 Catalina support (BKR-1621) # [4.14.1](https://github.com/voxpupuli/beaker/compare/4.14.0...4.14.1) - 2019-11-18 ### Fixed - `fips_mode?` detection (#1607) # [4.14.0](https://github.com/voxpupuli/beaker/compare/4.13.1...4.14.0) - 2019-11-12 ### Added - Pre-built steps output stacktraces when aborted (QENG-7466) # [4.13.1](https://github.com/voxpupuli/beaker/compare/4.13.0...4.13.1) - 2019-10-07 ### Fixed - Use correct platform variant for FIPS repo configs download (BKR-1616) # [4.13.0](https://github.com/voxpupuli/beaker/compare/4.12.0...4.13.0) - 2019-09-16 ### Added - Host `enable_remote_rsyslog` method (QENG-7466) # [4.12.0](https://github.com/voxpupuli/beaker/compare/4.11.1...4.12.0) - 2019-08-14 ### Added - redhatfips as a recognized platform (PE-27037) # [4.11.1](https://github.com/voxpupuli/beaker/compare/4.11.0...4.11.1) - 2019-08-13 ### Changed - `host.down?`'s wait from a fibonacci to a constant wait (BKR-1595) # [4.11.0](https://github.com/voxpupuli/beaker/compare/4.10.0...4.11.0) - 2019-07-22 ### Added - FIPS detection host method (BKR-1604) - PassTest exception catching for standard reporting # [4.10.0](https://github.com/voxpupuli/beaker/compare/4.9.0...4.10.0) - 2019-07-01 ### Added - Down & Up Checking to Host#reboot (BKR-1595) # [4.9.0](https://github.com/voxpupuli/beaker/compare/4.8.0...4.9.0) - 2019-06-19 ### Changed - SSH Connection failure backoff shortened (BKR-1599) # [4.8.0](https://github.com/voxpupuli/beaker/compare/4.7.0...4.8.0) - 2019-04-17 ### Added - Support for Fedora >= 30 (BKR-1589) - Codenames for Ubuntu 18.10, 19.04, and 19.10 ### Changed - Remove "repos-pe" prefix for repo filenames # [4.7.0](https://github.com/voxpupuli/beaker/compare/4.6.0...4.7.0) - 2019-04-17 ### Added - Provide for OpenSSL 1.1.x+ support - enable Solaris10Sparc pkgutil SSL CA2 (IMAGES-844) ### Changed - update pry-byebug dependency 3.4.2->3.6 (BKR-1568) - disabling hostkey checks for cisco hosts (QENG-7108) - Change behavior of ruby versioning to accept job-parameter RUBY\_VER - Change subcommand pre-suite to install ruby 2.3.1 # [4.6.0](https://github.com/voxpupuli/beaker/compare/4.5.0...4.6.0) - 2019.03.07 ### Added - Codename for Debian 10 'Buster' # [4.5.0](https://github.com/voxpupuli/beaker/compare/4.4.0...4.5.0) - 2019.01.23 ### Changed - Do not mirror profile.d on Debian (BKR-1559) # [4.4.0](https://github.com/voxpupuli/beaker/compare/4.3.0...4.4.0) - 2019.01.09 ### Added - Return root considerations for appending on nexus devices (BKR-1562) - Permit user environment on osx-10.14 (BKR-1534) - Add host helpers for working with files (BKR-1560) ### Changed - Replace ntpdate with crony on RHEL-8 (BKR-1555) # [4.3.0](https://github.com/voxpupuli/beaker/compare/4.2.0...4.3.0) - 2018.12.12 ### Added - Use zypper to install RPM packages on SLES (PA-2336) - Add only-fails capability to beaker (BKR-1523) # [4.2.0](https://github.com/voxpupuli/beaker/compare/4.1.0...4.2.0) - 2018.11.28 ### Added - `BEAKER_HYPERVISOR` environment variable to choose the beaker-hostgenerator hypervisor ### Changed - Handling of vsh appended commands for cisco_nexus (BKR-1556) - Acceptance tests: Add backoffs to other create_remote_file test ### Fixed - Don't always start a new container with docker (can be reused between invocations of the provision and exec beaker subcommands) (BKR-1547) - Recursively remove unpersisted subcommand options (BKR-1549) # [4.1.0](https://github.com/voxpupuli/beaker/compare/4.0.0...4.1.0) - 2018.10.25 ### Added - `--preserve-state` flag will preserve a given host options hash across subcommand runs(BKR-1541) ### Changed - Added additional tests for EL-like systems and added 'redhat' support where necessary - Test if puppet module is installed in '/' and avoid stripping of path seperator # [4.0.0](https://github.com/voxpupuli/beaker/compare/3.37.0...4.0.0) - 2018-08-06 ### Fixed - `host.rsync_to` throws `Beaker::Host::CommandFailure` if rsync call fails (BKR-463) - `host.rsync_to` throws `Beaker::Host::CommandFailure` if rsync does not exist on remote system (BKR-462) - `host.rsync_to` now check through configured SSH keys to use the first valid one - Updated some `Beaker::Host` methods to always return a `Result` object ### Added - Adds `Beaker::Host#chown`, `#chgrp`, and `#ls_ld` methods (BKR-1499) - `#uninstall_package` host helper, to match `#install_package` - `Host.uninstall_package` for FreeBSD - Now easily check a command's exit status by calling `Result.success?()` for a simple, truthy result. No need to validate the exit code manually. ### Changed - `#set_env` no longer calls `#configure_type_defaults_on` - `beaker-puppet` DSL Extension Library has been formally split into a standard DSL Extension Library and removed as a dependency from Beaker. Please see our [upgrade guidelines](docs/how_to/upgrade_from_3_to_4.md). - Beaker's Hypervisor Libraries have been removed as dependencies. Please see our [upgrade guidelines](docs/how_to/upgrade_from_3_to_4.md). ### Removed - `PEDefaults` has been moved to `beaker-pe` # [3.37.0](https://github.com/voxpupuli/beaker/compare/3.36.0...3.37.0) - 2018-07-11 ### Fixed - Exit early on --help/--version/--parse-only arguments instead of partial dry-run ### Added - `Beaker::Shared::FogCredentials.get_fog_credentials()` to parse .fog credential files ### Changed - `beaker-pe` is no longer automagically included. See [the upgrade guide](/docs/how_to/upgrade_from_3_to_4.md}) for more info - `beaker-puppet` is no longer required as a dependency # [3.36.0](https://github.com/voxpupuli/beaker/compare/3.35.0...3.36.0) - 2018-06-18 ### Fixed - Raise `ArgumentError` when passing `role = nil` to `only_host_with_role()` or `find_at_most_one_host_with_role()` - Use `install_package_with_rpm` in `add_el_extras` ### Added - Installation instructions for contributors - Markdown formatting guidelines for `docs/` - Glossary for project jargon in [`docs/concepts/glossary.md`](docs/concepts/glossary.md) - Use AIX 6.1 packages everywhere for puppet6 # [3.35.0](https://github.com/voxpupuli/beaker/compare/3.34.0...3.35.0) - 2018-05-16 ### Fixed - Report accurate location of generated smoke test - Accept comma-separated tests for exec subcommand ### Added - Added optional ability to use ERB in nodeset YAML files # [3.34.0](https://github.com/voxpupuli/beaker/compare/3.33.0...3.34.0) - 2018-03-26 ### Fixed - Recursively glob the tests directory ### Added - Codename for Ubuntu 18.04 'Bionic' # [3.33.0](https://github.com/voxpupuli/beaker/compare/3.32.0...3.33.0) - 2018-03-07 ### Changed - Use relative paths for beaker exec # [3.32.0](https://github.com/voxpupuli/beaker/compare/3.31.0...3.32.0) - 2018-02-22 ### Changed - Fully qualify sles ssh restart cmd - Deprecated deploy_package_repo methods - Configuration of host type in host_prebuilt_steps ### Added - Added missing beaker options for subcommand passthorugh # [3.31.0](https://github.com/voxpupuli/beaker/compare/3.30.0...3.31.0) - 2018-01-22 ### Changed - Clean up ssh paranoid setting deprecation warnings ### Added - Add macOS 10.13 support # [3.30.0](https://github.com/voxpupuli/beaker/compare/3.29.0...3.30.0) - 2018-01-10 ### Changed - Use `host.hostname` when combining options host_hash with host instance options ### Removed - `amazon` as a platform value ### Added - Load project options from .beaker.yml # [3.29.0](https://github.com/voxpupuli/beaker/compare/3.28.0...3.29.0) - 2017-11-16 ### Added - Adding default to read fog credentials # [3.28.0](https://github.com/voxpupuli/beaker/compare/3.27.0...3.28.0) - 2017-11-01 ### Fixed - corruption of `opts[:ignore]` when using `rsync` # [3.27.0](https://github.com/voxpupuli/beaker/compare/3.26.0...3.27.0) - 2017-10-19 ### Added - support amazon as a platform - add codenames for MacOS 10.13 and Ubuntu Artful # [3.26.0](https://github.com/voxpupuli/beaker/compare/3.25.0...3.26.0) - 2017-10-05 ### Added - concept of `manual_test` and `manual_step` # [3.25.0](https://github.com/voxpupuli/beaker/compare/3.24.0...3.25.0) - 2017-09-26 beaker-4.30.0/LICENSE000066400000000000000000000262111407603575700140540ustar00rootroot00000000000000 Copyright (C) 2011-2015 Puppet Labs Inc Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. beaker-4.30.0/README.md000066400000000000000000000101661407603575700143300ustar00rootroot00000000000000# Beaker [![License](https://img.shields.io/github/license/voxpupuli/beaker.svg)](https://github.com/voxpupuli/beaker/blob/master/LICENSE) [![Test](https://github.com/voxpupuli/beaker/actions/workflows/test.yml/badge.svg)](https://github.com/voxpupuli/beaker/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/voxpupuli/beaker/branch/master/graph/badge.svg?token=Mypkl78hvK)](https://codecov.io/gh/voxpupuli/beaker) [![Release](https://github.com/voxpupuli/beaker/actions/workflows/release.yml/badge.svg)](https://github.com/voxpupuli/beaker/actions/workflows/release.yml) [![RubyGem Version](https://img.shields.io/gem/v/beaker.svg)](https://rubygems.org/gems/beaker) [![RubyGem Downloads](https://img.shields.io/gem/dt/beaker.svg)](https://rubygems.org/gems/beaker) [![Donated by Puppet Inc](https://img.shields.io/badge/donated%20by-Puppet%20Inc-fb7047.svg)](#transfer-notice) Beaker is a test harness focused on acceptance testing via interactions between multiple (virtual) machines. It provides platform abstraction between different Systems Under Test (SUTs), and it can also be used as a virtual machine provisioner - setting up machines, running any commands on those machines, and then exiting. Beaker runs tests written in Ruby, and provides additional Domain-Specific Language (DSL) methods. This gives you access to all standard Ruby along with acceptance testing specific commands. # Installation See [Beaker Installation](docs/tutorials/installation.md). # Documentation Documentation for Beaker can be found in this repository in [the docs/ folder](docs/README.md). ## Table of Contents - [Tutorials](docs/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. - [Concepts](docs/concepts) discuss key topics and concepts at a fairly high level and provide useful background information and explanation. - [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. - [How-to guides](docs/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. # Beaker Libraries Beaker functionality has been extended through the use of libraries available as gems. See the [complete list](docs/concepts/beaker_libraries.md) for available gems. See the [beaker-template documentation](https://github.com/puppetlabs/beaker-template/blob/master/README.md) for documentation on creating beaker-libraries. # Support & Issues Please log tickets and issues at our [Beaker Issue Tracker](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20BKR). In addition there is an active #puppet-dev channel on Freenode. For additional information on filing tickets, please check out our [CONTRIBUTOR doc](CONTRIBUTING.md), and for ticket lifecycle information, check out our [ticket process doc](docs/concepts/ticket_process.md). # Contributing If you'd like to contribute improvements to Beaker, please see [CONTRIBUTING](CONTRIBUTING.md). # Maintainers For information on project maintainers, please check out our [CODEOWNERS doc](CODEOWNERS). ## Transfer Notice This plugin was originally authored by [Puppet Inc](http://puppet.com). The maintainer preferred that Puppet Community take ownership of the module for future improvement and maintenance. Existing pull requests and issues were transferred over, please fork and continue to contribute here. Previously: https://github.com/puppetlabs/beaker ## License This gem is licensed under the Apache-2 license. ## Release information To make a new release, please do: * update the version in the gemspec file * Install gems with `bundle install --with release --path .vendor` * generate the changelog with `bundle exec rake changelog` * Check if the new version matches the closed issues/PRs in the changelog * Create a PR with it * After it got merged, push a tag. GitHub actions will do the actual release to rubygems and GitHub Packages beaker-4.30.0/Rakefile000066400000000000000000000211101407603575700145050ustar00rootroot00000000000000require 'open3' require 'securerandom' require 'beaker-hostgenerator' require 'beaker' HOSTS_PRESERVED = 'log/latest/hosts_preserved.yml' task :default => [ 'test:spec' ] task :test do Rake::Task['test:spec'].invoke end task :spec do Rake::Task['test:spec'].invoke end task :acceptance => ['test:base', 'test:puppetgit', 'test:hypervisor'] task :yard do Rake::Task['docs:gen'].invoke end task :history do Rake::Task['history:gen'].invoke end module HarnessOptions defaults = { :tests => ['tests'], :log_level => 'debug', :preserve_hosts => 'onfail', } DEFAULTS = defaults def self.get_options(file_path) puts "Attempting to merge config file: #{file_path}" if File.exists? file_path options = eval(File.read(file_path), binding) else puts "No options file found at #{File.expand_path(file_path)}... skipping" end options || {} end def self.get_mode_options(mode) get_options("./acceptance/config/#{mode}/acceptance-options.rb") end def self.get_local_options get_options('./acceptance/local_options.rb') end def self.final_options(mode, intermediary_options = {}) mode_options = get_mode_options(mode) local_overrides = get_local_options final_options = DEFAULTS.merge(mode_options) final_options.merge!(intermediary_options) final_options.merge!(local_overrides) end end def hosts_file_env ENV['BEAKER_HOSTS'] end def hosts_opt(use_preserved_hosts=false) if use_preserved_hosts "--hosts=#{HOSTS_PRESERVED}" else if hosts_file_env "--hosts=#{hosts_file_env}" else "--hosts=tmp/#{HOSTS_FILE}" end end end def agent_target ENV['TEST_TARGET'] || 'redhat7-64af' end def master_target ENV['MASTER_TEST_TARGET'] || 'redhat7-64default.mdcal' end def test_targets ENV['LAYOUT'] || "#{master_target}-#{agent_target}" end HOSTS_FILE = "#{test_targets}-#{SecureRandom.uuid}.yaml" def beaker_test(mode = :base, options = {}) preserved_hosts_mode = options[:hosts] == HOSTS_PRESERVED final_options = HarnessOptions.final_options(mode, options) options_opt = "" # preserved hosts can not be used with an options file (BKR-670) # one can still use OPTIONS if !preserved_hosts_mode options_file = 'merged_options.rb' options_opt = "--options-file=#{options_file}" File.open(options_file, 'w') do |merged| merged.puts <<-EOS # Copy this file to local_options.rb and adjust as needed if you wish to run # with some local overrides. EOS merged.puts(final_options) end end tests = ENV['TESTS'] || ENV['TEST'] tests_opt = "" tests_opt = "--tests=#{tests}" if tests overriding_options = ENV['OPTIONS'].to_s args = [options_opt, hosts_opt(preserved_hosts_mode), tests_opt, *overriding_options.split(' ')].compact sh("beaker", *args) end namespace :test do USAGE = <<-EOS You may set BEAKER_HOSTS=config/nodes/foo.yaml or include it in an acceptance-options.rb for Beaker, or specify TEST_TARGET in a form beaker-hostgenerator accepts, e.g. ubuntu1504-64a. You may override the default master test target by specifying MASTER_TEST_TARGET. You may set TESTS=path/to/test,and/more/tests. You may set additional Beaker OPTIONS='--more --options' If there is a Beaker options hash in a ./acceptance/local_options.rb, it will be included. Commandline options set through the above environment variables will override settings in this file. EOS desc 'Run specs and check for deprecation warnings' task :spec do original_dir = Dir.pwd Dir.chdir( File.expand_path(File.dirname(__FILE__)) ) exit_status = 1 output = '' Open3.popen3("bundle exec rspec") {|stdin, stdout, stderr, wait_thr| while line = stdout.gets puts line end output = stdout if not wait_thr.value.success? fail "Failed to 'bundle exec rspec' (exit status: #{wait_thr.value})" end exit_status = wait_thr.value } if exit_status != /0/ #check for deprecation warnings if output =~ /Deprecation Warnings/ fail "DEPRECATION WARNINGS in spec generation, please fix!" end end Dir.chdir( original_dir ) end desc <<-EOS Run the base beaker acceptance tests #{USAGE} EOS task :base => 'gen_hosts' do beaker_test(:base) end desc <<-EOS Run the subcommand beaker acceptance tests #{USAGE} EOS task :subcommands => 'gen_hosts' do beaker_test(:subcommands) end desc <<-EOS Run the hypervisor beaker acceptance tests #{USAGE} EOS task :hypervisor => 'gen_hosts' do beaker_test(:hypervisor) end desc 'Generate Beaker Host Config File' task :gen_hosts do if hosts_file_env next end cli = BeakerHostGenerator::CLI.new([test_targets]) FileUtils.mkdir_p('tmp') # -p ignores when dir already exists File.open("tmp/#{HOSTS_FILE}", 'w') do |fh| fh.print(cli.execute) end end end ########################################################### # # History Tasks # ########################################################### namespace :history do desc 'Generate HISTORY.md' task :gen do original_dir = Dir.pwd Dir.chdir( File.expand_path(File.dirname(__FILE__)) ) output = `bundle exec ruby history.rb .` puts output if output !~ /success/ raise "History generation failed" end Dir.chdir( original_dir ) end end ########################################################### # # Documentation Tasks # ########################################################### DOCS_DIR = 'yard_docs' DOCS_DAEMON = "yard server --reload --daemon --docroot #{DOCS_DIR}" FOREGROUND_SERVER = "bundle exec yard server --reload --verbose lib/beaker --docroot #{DOCS_DIR}" def running?( cmdline ) ps = `ps -ef` found = ps.lines.grep( /#{Regexp.quote( cmdline )}/ ) if found.length > 1 raise StandardError, "Found multiple YARD Servers. Don't know what to do." end yes = found.empty? ? false : true return yes, found.first end def pid_from( output ) output.squeeze(' ').strip.split(' ')[1] end desc 'Start the documentation server in the foreground' task :docs => 'docs:clear' do original_dir = Dir.pwd Dir.chdir( File.expand_path(File.dirname(__FILE__)) ) sh FOREGROUND_SERVER Dir.chdir( original_dir ) end namespace :docs do desc 'Clear the generated documentation cache' task :clear do original_dir = Dir.pwd Dir.chdir( File.expand_path(File.dirname(__FILE__)) ) sh "rm -rf #{DOCS_DIR}" Dir.chdir( original_dir ) end desc 'Generate static documentation' task :gen => 'docs:clear' do original_dir = Dir.pwd Dir.chdir( File.expand_path(File.dirname(__FILE__)) ) output = `bundle exec yard doc -o #{DOCS_DIR}` puts output if output =~ /\[warn\]|\[error\]/ fail "Errors/Warnings during yard documentation generation" end Dir.chdir( original_dir ) end desc 'Run the documentation server in the background, alias `bg`' task :background => 'docs:clear' do yes, output = running?( DOCS_DAEMON ) if yes puts "Not starting a new YARD Server..." puts "Found one running with pid #{pid_from( output )}." else original_dir = Dir.pwd Dir.chdir( File.expand_path(File.dirname(__FILE__)) ) sh "bundle exec #{DOCS_DAEMON}" Dir.chdir( original_dir ) end end task(:bg) { Rake::Task['docs:background'].invoke } desc 'Check the status of the documentation server' task :status do yes, output = running?( DOCS_DAEMON ) if yes pid = pid_from( output ) puts "Found a YARD Server running with pid #{pid}" else puts "Could not find a running YARD Server." end end desc "Stop a running YARD Server" task :stop do yes, output = running?( DOCS_DAEMON ) if yes pid = pid_from( output ) puts "Found a YARD Server running with pid #{pid}" `kill #{pid}` puts "Stopping..." yes, output = running?( DOCS_DAEMON ) if yes `kill -9 #{pid}` yes, output = running?( DOCS_DAEMON ) if yes puts "Could not Stop Server!" else puts "Server stopped." end else puts "Server stopped." end else puts "Could not find a running YARD Server" end end end begin require 'rubygems' require 'github_changelog_generator/task' GitHubChangelogGenerator::RakeTask.new :changelog do |config| config.exclude_labels = %w{duplicate question invalid wontfix wont-fix skip-changelog} config.user = 'voxpupuli' config.project = 'beaker' gem_version = Gem::Specification.load("#{config.project}.gemspec").version config.future_release = gem_version end rescue LoadError end beaker-4.30.0/acceptance/000077500000000000000000000000001407603575700151335ustar00rootroot00000000000000beaker-4.30.0/acceptance/config/000077500000000000000000000000001407603575700164005ustar00rootroot00000000000000beaker-4.30.0/acceptance/config/acceptance-options.rb000066400000000000000000000002221407603575700225000ustar00rootroot00000000000000{ :load_path => File.join('acceptance', 'lib'), :ssh => { :keys => ["id_rsa_acceptance", "#{ENV['HOME']}/.ssh/id_rsa-acceptance"], }, } beaker-4.30.0/acceptance/config/base/000077500000000000000000000000001407603575700173125ustar00rootroot00000000000000beaker-4.30.0/acceptance/config/base/acceptance-options.rb000066400000000000000000000001711407603575700234150ustar00rootroot00000000000000{ :tests => 'acceptance/tests/base' }.merge(eval File.read('acceptance/config/acceptance-options.rb'))beaker-4.30.0/acceptance/config/hypervisor/000077500000000000000000000000001407603575700206125ustar00rootroot00000000000000beaker-4.30.0/acceptance/config/hypervisor/acceptance-options.rb000066400000000000000000000001771407603575700247230ustar00rootroot00000000000000{ :tests => 'acceptance/tests/hypervisor' }.merge(eval File.read('acceptance/config/acceptance-options.rb'))beaker-4.30.0/acceptance/config/subcommands/000077500000000000000000000000001407603575700207135ustar00rootroot00000000000000beaker-4.30.0/acceptance/config/subcommands/acceptance-options.rb000066400000000000000000000002521407603575700250160ustar00rootroot00000000000000{ :pre_suite => 'acceptance/pre_suite/subcommands/', :tests => 'acceptance/tests/subcommands/' }.merge(eval File.read('acceptance/config/acceptance-options.rb')) beaker-4.30.0/acceptance/fixtures/000077500000000000000000000000001407603575700170045ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/README.md000066400000000000000000000001321407603575700202570ustar00rootroot00000000000000# Beaker Acceptance Tests: fixtures Any files needed to execute Beaker acceptance tests. beaker-4.30.0/acceptance/fixtures/files/000077500000000000000000000000001407603575700201065ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/files/failing_shell_script.txt000066400000000000000000000000511407603575700250270ustar00rootroot00000000000000 # this shell script always fails exit 1 beaker-4.30.0/acceptance/fixtures/files/retry_script.txt000066400000000000000000000006311407603575700234000ustar00rootroot00000000000000 # This script which will fail the first `RETRY_LIMIT` times when run and then # will exit successfully on later runs. It will store counter state in a file # in the provided `BASEDIR` directory. set -x BASEDIR=${1} RETRY_LIMIT=${2} current=`cat ${BASEDIR}/value.txt || echo '0'` current=$((current+1)) echo "${current}" > ${BASEDIR}/value.txt if [ "$current" -gt "${RETRY_LIMIT}" ]; then exit 0; fi exit 1 beaker-4.30.0/acceptance/fixtures/files/shell_script_with_output.txt000066400000000000000000000001101407603575700260050ustar00rootroot00000000000000 # this shell script always succeeds, and produces output echo "output" beaker-4.30.0/acceptance/fixtures/files/simple_text_file.txt000066400000000000000000000000611407603575700242000ustar00rootroot00000000000000This is a simple text file. It has three lines. beaker-4.30.0/acceptance/fixtures/module/000077500000000000000000000000001407603575700202715ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/Gemfile000066400000000000000000000005341407603575700215660ustar00rootroot00000000000000source 'https://rubygems.org' puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3'] gem 'puppet', puppetversion gem 'puppetlabs_spec_helper', '>= 0.1.0' gem 'puppet-lint', '>= 0.3.2' gem 'facter', '>= 1.7.0' group :system_tests do gem 'beaker-rspec', :require => false gem 'serverspec', :require => false end beaker-4.30.0/acceptance/fixtures/module/README.md000066400000000000000000000055131407603575700215540ustar00rootroot00000000000000# demo #### Table of Contents 1. [Overview](#overview) 2. [Module Description - What the module does and why it is useful](#module-description) 3. [Setup - The basics of getting started with demo](#setup) * [What demo affects](#what-demo-affects) * [Setup requirements](#setup-requirements) * [Beginning with demo](#beginning-with-demo) 4. [Usage - Configuration options and additional functionality](#usage) 5. [Reference - An under-the-hood peek at what the module is doing and how](#reference) 5. [Limitations - OS compatibility, etc.](#limitations) 6. [Development - Guide for contributing to the module](#development) ## Overview A one-maybe-two sentence summary of what the module does/what problem it solves. This is your 30 second elevator pitch for your module. Consider including OS/Puppet version it works with. ## Module Description If applicable, this section should have a brief description of the technology the module integrates with and what that integration enables. This section should answer the questions: "What does this module *do*?" and "Why would I use it?" If your module has a range of functionality (installation, configuration, management, etc.) this is the time to mention it. ## Setup ### What demo affects * A list of files, packages, services, or operations that the module will alter, impact, or execute on the system it's installed on. * This is a great place to stick any warnings. * Can be in list or paragraph form. ### Setup Requirements **OPTIONAL** If your module requires anything extra before setting up (pluginsync enabled, etc.), mention it here. ### Beginning with demo The very basic steps needed for a user to get the module up and running. If your most recent release breaks compatibility or requires particular steps for upgrading, you may wish to include an additional section here: Upgrading (For an example, see http://forge.puppetlabs.com/puppetlabs/firewall). ## Usage Put the classes, types, and resources for customizing, configuring, and doing the fancy stuff with your module here. ## Reference Here, list the classes, types, providers, facts, etc contained in your module. This section should include all of the under-the-hood workings of your module so people know what the module is touching on their system but don't need to mess with things. (We are working on automating this section!) ## Limitations This is where you list OS compatibility, version compatibility, etc. ## Development Since your module is awesome, other users will want to play with it. Let them know what the ground rules for contributing are. ## Release Notes/Contributors/Etc **Optional** If you aren't using changelog, put your release notes here (though you should consider using changelog). You may also add any additional sections you feel are necessary or important to include here. Please use the `## ` header. beaker-4.30.0/acceptance/fixtures/module/Rakefile000066400000000000000000000011711407603575700217360ustar00rootroot00000000000000require 'rubygems' require 'puppetlabs_spec_helper/rake_tasks' require 'puppet-lint/tasks/puppet-lint' PuppetLint.configuration.send('disable_80chars') PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"] desc "Validate manifests, templates, and ruby files" task :validate do Dir['manifests/**/*.pp'].each do |manifest| sh "puppet parser validate --noop #{manifest}" end Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file| sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/ end Dir['templates/**/*.erb'].each do |template| sh "erb -P -x -T '-' #{template} | ruby -c" end end beaker-4.30.0/acceptance/fixtures/module/lib/000077500000000000000000000000001407603575700210375ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/lib/empty.txt000066400000000000000000000000451407603575700227350ustar00rootroot00000000000000this is where module code would live beaker-4.30.0/acceptance/fixtures/module/manifests/000077500000000000000000000000001407603575700222625ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/manifests/init.pp000066400000000000000000000017051407603575700235710ustar00rootroot00000000000000# == Class: demo # # Full description of class demo here. # # === Parameters # # Document parameters here. # # [*sample_parameter*] # Explanation of what this parameter affects and what it defaults to. # e.g. "Specify one or more upstream ntp servers as an array." # # === Variables # # Here you should define a list of variables that this module would require. # # [*sample_variable*] # Explanation of how this variable affects the funtion of this class and if # it has a default. e.g. "The parameter enc_ntp_servers must be set by the # External Node Classifier as a comma separated list of hostnames." (Note, # global variables should be avoided in favor of class parameters as # of Puppet 2.6.) # # === Examples # # class { 'demo': # servers => [ 'pool.ntp.org', 'ntp.local.company.com' ], # } # # === Authors # # Author Name # # === Copyright # # Copyright 2015 Your name here, unless otherwise noted. # class demo { } beaker-4.30.0/acceptance/fixtures/module/metadata.json000066400000000000000000000005001407603575700227370ustar00rootroot00000000000000{ "name": "user-demo", "version": "0.0.1", "author": "alice.nodelman", "summary": "For use in testing beaker.", "license": "Apache 2.0", "source": "no_source", "project_page": "no_page", "issues_url": "no_url", "dependencies": [ {"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"} ] } beaker-4.30.0/acceptance/fixtures/module/spec/000077500000000000000000000000001407603575700212235ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/000077500000000000000000000000001407603575700233115ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/demo_spec.rb000066400000000000000000000033621407603575700256000ustar00rootroot00000000000000require 'spec_helper_acceptance' describe "my tests" do # an example using the beaker DSL # use http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL it "should say hello!" do result = shell( 'echo hello' ) expect(result.stdout).to match(/hello/) end # an example using Serverspec # use http://serverspec.org/resource_types.html describe package('puppet') do it { should be_installed } end it "can create and confirm a file" do shell( 'rm -f demo.txt' ) create_remote_file(default, 'demo.txt', 'foo\nfoo\nfoo\n') shell( 'grep foo demo.txt' ) shell( 'grep bar demo.txt', :acceptable_exit_codes => [1] ) end it "should be able to apply manifests" do manifest_1 = "user {'foo': ensure => present,}" manifest_2 = "user {'foo': ensure => absent,}" manifest_3 = "user {'root': ensure => present,}" apply_manifest( manifest_1, :expect_changes => true ) apply_manifest( manifest_2, :expect_changes => true ) apply_manifest( manifest_3 ) end describe service('sshd') do it { should be_running } end describe user('root') do it { should exist } end describe user('foo') do it { should_not exist } end context "can use both serverspec and Beaker DSL" do it "can create a file" do shell( 'rm -f /tmp/demo.txt' ) manifest = "file {'demofile': path => '/tmp/demo.txt', ensure => present, mode => 0640, content => \"this is my file.\", }" apply_manifest(manifest, :expect_changes => true) end describe file('/tmp/demo.txt') do it { should be_file } end describe file('/tmp/demo.txt') do it { should contain 'this is my file.' } end end end beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/000077500000000000000000000000001407603575700251355ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/centos-59-x64.yml000066400000000000000000000003701407603575700300250ustar00rootroot00000000000000HOSTS: centos-59-x64: roles: - master platform: el-5-x86_64 box : centos-59-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-59-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: git beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/centos-64-x64-pe.yml000066400000000000000000000004321407603575700304220ustar00rootroot00000000000000HOSTS: centos-64-x64: roles: - master - database - dashboard platform: el-6-x86_64 box : centos-64-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: pe beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/centos-64-x64.yml000066400000000000000000000003711407603575700300220ustar00rootroot00000000000000HOSTS: centos-64-x64: roles: - master platform: el-6-x86_64 box : centos-64-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/centos-65-x64.yml000066400000000000000000000003721407603575700300240ustar00rootroot00000000000000HOSTS: centos-65-x64: roles: - master platform: el-6-x86_64 box : centos-65-x64-vbox436-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-65-x64-virtualbox-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/default.yml000066400000000000000000000003711407603575700273050ustar00rootroot00000000000000HOSTS: centos-64-x64: roles: - master platform: el-6-x86_64 box : centos-64-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/centos-64-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/fedora-18-x64.yml000066400000000000000000000003761407603575700277730ustar00rootroot00000000000000HOSTS: fedora-18-x64: roles: - master platform: fedora-18-x86_64 box : fedora-18-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/fedora-18-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/internal-vpool.yml000066400000000000000000000007251407603575700306350ustar00rootroot00000000000000HOSTS: centos-7-x86_64-master: roles: - agent - dashboard - database - master hypervisor: vcloud platform: centos-7-x86_64 template: Delivery/Quality Assurance/Templates/vCloud/centos-7-x86_64 CONFIG: pooling_api: http://vcloud.delivery.puppetlabs.net datastore: instance0 folder: Delivery/Quality Assurance/Staging/Dynamic resourcepool: delivery/Quality Assurance/Staging/Dynamic nfs_server: none consoleport: 443 beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/sles-11-x64.yml000066400000000000000000000004011407603575700274570ustar00rootroot00000000000000HOSTS: sles-11-x64.local: roles: - master platform: sles-11-x64 box : sles-11sp1-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/sles-11sp1-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-10044-x64.yml000066400000000000000000000004361407603575700315760ustar00rootroot00000000000000HOSTS: ubuntu-server-10044-x64: roles: - master platform: ubuntu-10.04-amd64 box : ubuntu-server-10044-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-10044-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-12042-x64.yml000066400000000000000000000004361407603575700315760ustar00rootroot00000000000000HOSTS: ubuntu-server-12042-x64: roles: - master platform: ubuntu-12.04-amd64 box : ubuntu-server-12042-x64-vbox4210-nocm box_url : http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-server-12042-x64-vbox4210-nocm.box hypervisor : vagrant CONFIG: type: foss beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-1404-x64.yml000066400000000000000000000004201407603575700315070ustar00rootroot00000000000000HOSTS: ubuntu-server-1404-x64: roles: - master platform: ubuntu-14.04-amd64 box : puppetlabs/ubuntu-14.04-64-nocm box_url : https://vagrantcloud.com/puppetlabs/ubuntu-14.04-64-nocm hypervisor : vagrant CONFIG: log_level : debug type: git beaker-4.30.0/acceptance/fixtures/module/spec/acceptance/nodesets/ubuntu-server-14042-x64.yml000066400000000000000000000006511407603575700315770ustar00rootroot00000000000000HOSTS: ubuntu1404: roles: - agent platform: ubuntu-14.04-amd64 template: ubuntu-1404-x86_64 hypervisor: vcloud CONFIG: type: foss keyfile: ~/.ssh/id_rsa-acceptance nfs_server: none consoleport: 443 datastore: instance0 folder: Delivery/Quality Assurance/Enterprise/Dynamic resourcepool: delivery/Quality Assurance/Enterprise/Dynamic pooling_api: http://vcloud.delivery.puppetlabs.net/ beaker-4.30.0/acceptance/fixtures/module/spec/classes/000077500000000000000000000000001407603575700226605ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/spec/classes/init_spec.rb000066400000000000000000000002141407603575700251570ustar00rootroot00000000000000require 'spec_helper' describe 'demo' do context 'with defaults for all parameters' do it { should contain_class('demo') } end end beaker-4.30.0/acceptance/fixtures/module/spec/spec_helper.rb000066400000000000000000000000641407603575700240410ustar00rootroot00000000000000require 'puppetlabs_spec_helper/module_spec_helper' beaker-4.30.0/acceptance/fixtures/module/spec/spec_helper_acceptance.rb000066400000000000000000000017761407603575700262220ustar00rootroot00000000000000require 'beaker-rspec' unless ENV['RS_PROVISION'] == 'no' or ENV['BEAKER_provision'] == 'no' # This will install the latest available package on el and deb based # systems fail on windows and osx, and install via gem on other *nixes foss_opts = {:default_action => 'gem_install'} if default.is_pe?; then install_pe; else install_puppet(foss_opts); end hosts.each do |host| unless host.is_pe? on host, "/bin/echo '' > #{host.puppet('hiera_config')}" end on host, "mkdir -p #{host['distmoduledir']}" end end RSpec.configure do |c| # Project root proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) # Readable test descriptions c.formatter = :documentation # Configure all nodes in nodeset c.before :suite do hosts.each do |host| on host, "mkdir -p #{host['distmoduledir']}/demo" %w(lib manifests metadata.json).each do |file| scp_to host, "#{proj_root}/#{file}", "#{host['distmoduledir']}/ntp" end end end end beaker-4.30.0/acceptance/fixtures/module/tests/000077500000000000000000000000001407603575700214335ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/tests/init.pp000066400000000000000000000007721407603575700227450ustar00rootroot00000000000000# The baseline for module testing used by Puppet Labs is that each manifest # should have a corresponding test manifest that declares that class or defined # type. # # Tests are then run by using puppet apply --noop (to check for compilation # errors and view a log of events) or by fully applying the test in a virtual # environment (to compare the resulting system state to the desired state). # # Learn more about module testing here: # http://docs.puppetlabs.com/guides/tests_smoke.html # include demo beaker-4.30.0/acceptance/fixtures/module/vendor/000077500000000000000000000000001407603575700215665ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/vendor/bundle/000077500000000000000000000000001407603575700230375ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/vendor/bundle/ruby/000077500000000000000000000000001407603575700240205ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/module/vendor/bundle/ruby/gems.txt000066400000000000000000000000471407603575700255150ustar00rootroot00000000000000test gems would live in this directory beaker-4.30.0/acceptance/fixtures/package/000077500000000000000000000000001407603575700203775ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/package/deb/000077500000000000000000000000001407603575700211315ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-jessie.list000066400000000000000000000001241407603575700277520ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/jessie jessie PC1 beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-lucid.list000066400000000000000000000001211407603575700275650ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/lucid lucid PC1beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-precise.list000066400000000000000000000001251407603575700301230ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/precise precise PC1beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-squeeze.list000066400000000000000000000001251407603575700301520ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/squeeze squeeze PC1beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-trusty.list000066400000000000000000000001241407603575700300420ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/trusty trusty PC1 beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-vivid.list000066400000000000000000000001221407603575700276070ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/vivid vivid PC1 beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-wheezy.list000066400000000000000000000001231407603575700300020ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/wheezy wheezy PC1beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-wily.list000066400000000000000000000001201407603575700274500ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/wily wily PC1 beaker-4.30.0/acceptance/fixtures/package/deb/pl-puppetserver-latest-xenial.list000066400000000000000000000001241407603575700277500ustar00rootroot00000000000000deb http://nightlies.puppetlabs.com/puppetserver-latest/repos/apt/xenial xenial PC1 beaker-4.30.0/acceptance/fixtures/package/rpm/000077500000000000000000000000001407603575700211755ustar00rootroot00000000000000beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-6-i386.repo000066400000000000000000000003361407603575700315150ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/6/PC1/i386/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-6-x86_64.repo000066400000000000000000000003401407603575700317550ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/6/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-7-i386.repo000066400000000000000000000003361407603575700315160ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/7/PC1/i386/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-el-7-x86_64.repo000066400000000000000000000003401407603575700317560ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/el/7/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-20-i386.repo000066400000000000000000000003441407603575700324300ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f20/PC1/i386/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-20-x86_64.repo000066400000000000000000000003461407603575700326770ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f20/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-21-i386.repo000066400000000000000000000003441407603575700324310ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f21/PC1/i386/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-21-x86_64.repo000066400000000000000000000003461407603575700327000ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f21/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-22-i386.repo000066400000000000000000000003441407603575700324320ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f22/PC1/i386/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-22-x86_64.repo000066400000000000000000000003461407603575700327010ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f22/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-23-i386.repo000066400000000000000000000003441407603575700324330ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f23/PC1/i386/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-23-x86_64.repo000066400000000000000000000003461407603575700327020ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f23/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-fedora-24-x86_64.repo000066400000000000000000000003461407603575700327030ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/fedora/f24/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57 beaker-4.30.0/acceptance/fixtures/package/rpm/pl-puppetserver-latest-repos-pe-sles-12-x86_64.repo000066400000000000000000000003421407603575700324020ustar00rootroot00000000000000[pl-puppetserver-latest] name=PL Repo for puppetserver at commit latest baseurl=http://nightlies.puppetlabs.com/puppetserver-latest/repos/sles/12/PC1/x86_64/ enabled=1 gpgcheck=1 gpgkey=http://nightlies.puppetlabs.com/07BB6C57beaker-4.30.0/acceptance/lib/000077500000000000000000000000001407603575700157015ustar00rootroot00000000000000beaker-4.30.0/acceptance/lib/helpers/000077500000000000000000000000001407603575700173435ustar00rootroot00000000000000beaker-4.30.0/acceptance/lib/helpers/test_helper.rb000066400000000000000000000064451407603575700222170ustar00rootroot00000000000000# NOTE: Currently scp failures throw a Net::SCP::Error exception in SSH connection # close, which ends up not being caught properly, and which ultimately results # in a RuntimeError. The SSH Connection is left in an unusable state and all # later remote commands will hang indefinitely. # # TODO: fix via: https://tickets.puppetlabs.com/browse/BKR-464 def test_scp_error_on_close? !!ENV["BEAKER_TEST_SCP_ERROR_ON_CLOSE"] end # Returns the absolute path where file fixtures are located. def fixture_path @fixture_path ||= File.expand_path(File.join(__FILE__, '..', '..', '..', 'fixtures', 'files')) end # Returns the contents of a named fixture file, to be found in `fixture_path`. def fixture_contents(fixture) fixture_file = Dir.entries(fixture_path).find { |e| /^#{fixture}$|#{fixture}\.[a-z]/ =~ e } File.read("#{fixture_path}/#{fixture_file}") end # Create a file on `host` in the `remote_path` with file name `filename`, # containing the contents of the fixture file named `fixture`. Returns # the full remote path to the created file, and the file contents. def create_remote_file_from_fixture(fixture, host, remote_path, filename) full_filename = File.join(remote_path, filename) contents = fixture_contents fixture create_remote_file host, full_filename, contents [ full_filename, contents ] end # Create a file locally, in the `local_path`, with file name `filename`, # containing the contents of the fixture file named `fixture`; optionally # setting the file permissions to `perms`. Returns the full path to the created # file, and the file contents. def create_local_file_from_fixture(fixture, local_path, filename, perms = nil) full_filename = File.join(local_path, filename) contents = fixture_contents fixture File.open(full_filename, "w") do |local_file| local_file.puts contents end FileUtils.chmod perms, full_filename if perms [ full_filename, contents ] end # Provide debugging information for tests which are known to fail intermittently # # issue_link - url of Jira issue documenting this intermittent test failure # args - Hash of debugging information (names => values) to output on a failure # block - block which intermittently fails # # Example # # fails_intermittently('https://tickets.puppetlabs.com/browse/QENG-2958', # '@host' => @host, 'user' => user, 'expected' => expected) do # assert_equal expected, user # end # # Absorbs any MiniTest::Assertion from a failing test assertion in the block. # This implies that the intermittent failure is caught and the suite will not # go red for this failure. Intended to be used with the Jenkins Build Failure # Analyzer (or similar), to detect these failures without failing the build. # # Returns the value of the yielded block. def fails_intermittently(issue_link, args = {}, &block) raise ArgumentError, "provide a Jira ticket link" unless issue_link raise ArgumentError, "a block is required" unless block_given? yield rescue MiniTest::Assertion, StandardError, SignalException => boom # we have a test failure! STDERR.puts "\n\nIntermittent test failure! See: #{issue_link}" if args.empty? STDERR.puts "No further debugging information available." else STDERR.puts "Debugging information:\n" args.keys.sort.each do |key| STDERR.puts "#{key} => #{args[key].inspect}" end end end beaker-4.30.0/acceptance/pre_suite/000077500000000000000000000000001407603575700171325ustar00rootroot00000000000000beaker-4.30.0/acceptance/pre_suite/README.md000066400000000000000000000002721407603575700204120ustar00rootroot00000000000000# Beaker Acceptance Tests: pre-suite Collection of pre-suites for SUT configuration before test execution. Options: * install PE * install FOSS Puppet * install beaker itself on a SUT beaker-4.30.0/acceptance/pre_suite/pe/000077500000000000000000000000001407603575700175365ustar00rootroot00000000000000beaker-4.30.0/acceptance/pre_suite/pe/install.rb000066400000000000000000000001311407603575700215240ustar00rootroot00000000000000# require beaker-pe to load in the additional DSL methods require 'beaker-pe' install_pe beaker-4.30.0/acceptance/pre_suite/subcommands/000077500000000000000000000000001407603575700214455ustar00rootroot00000000000000beaker-4.30.0/acceptance/pre_suite/subcommands/05_install_ruby.rb000066400000000000000000000026331407603575700250110ustar00rootroot00000000000000ruby_version, ruby_source = ENV['RUBY_VER'], "job parameter" unless ruby_version ruby_version = "2.4.1" ruby_source = "default" end test_name "Install and configure Ruby #{ruby_version} (from #{ruby_source}) on the SUT" do step 'Ensure that the default system is an el-based system' do # The pre-suite currently only supports el systems, and we should #fail early if the default platform is not a supported platform assert(default.platform.variant == 'el', "Expected the platform variant to be 'el', not #{default.platform.variant}") end step 'clean out current ruby and its dependencies' do on default, 'yum remove ruby ruby-devel -y' end # These steps install git, openssl, and wget step 'install development dependencies' do on default, 'yum groupinstall "Development Tools" -y' on default, 'yum install openssl-devel -y' on default, 'yum install wget -y' end step "download and install ruby #{ruby_version}" do on default, "wget http://cache.ruby-lang.org/pub/ruby/#{ruby_version[0..2]}/ruby-#{ruby_version}.tar.gz" on default, "tar xvfz ruby-#{ruby_version}.tar.gz" on default, "cd ruby-#{ruby_version};./configure" on default, "cd ruby-#{ruby_version};make" on default, "cd ruby-#{ruby_version};make install" end step 'update gem on the SUT and install bundler' do on default, 'gem update --system;gem install --force bundler' end end beaker-4.30.0/acceptance/pre_suite/subcommands/08_install_beaker.rb000066400000000000000000000017711407603575700252660ustar00rootroot00000000000000test_name 'Install beaker and checkout branch if necessary' do step 'Download the beaker git repo' do on default, 'git clone https://github.com/puppetlabs/beaker.git /opt/beaker/' end step 'Detect if checking out branch for testing and checkout' do if ENV['BEAKER_PULL_ID'] logger.notify "Pull Request detected, checking out PR branch" 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/*') on(default, "cd /opt/beaker/;git merge origin/pr/#{ENV['BEAKER_PULL_ID']}/head --no-edit") else logger.notify 'No PR branch detected, building from master' end end step 'Build the gem and install it on the local system' do build_output = on(default, 'cd /opt/beaker/;gem build beaker.gemspec').stdout version = build_output.match(/^ File: (.+)$/)[1] on(default, "cd /opt/beaker/;gem install #{version} --no-document; gem install beaker-vmpooler") end end beaker-4.30.0/acceptance/scripts/000077500000000000000000000000001407603575700166225ustar00rootroot00000000000000beaker-4.30.0/acceptance/scripts/all_but_host_test.sh000066400000000000000000000006131407603575700226740ustar00rootroot00000000000000#!/usr/bin/env bash top_level=$(git rev-parse --show-toplevel) acceptance_test_base="$top_level/acceptance/tests/base" find $acceptance_test_base -type f -name '*.rb' | grep -v host_test.rb | awk 'BEGIN { comma_index = 1 } { if (comma_index == 1) { comma_string = $0 } else { comma_string = comma_string "," $0 } comma_index = comma_index + 1 } END { print comma_string }' beaker-4.30.0/acceptance/tests/000077500000000000000000000000001407603575700162755ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/000077500000000000000000000000001407603575700172075ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/README.md000066400000000000000000000001441407603575700204650ustar00rootroot00000000000000# Beaker Acceptance Tests: base Tests that do not depend upon Puppet being installed on the SUTs. beaker-4.30.0/acceptance/tests/base/dsl/000077500000000000000000000000001407603575700177715ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/dsl/helpers/000077500000000000000000000000001407603575700214335ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/dsl/helpers/configuration_test.rb000066400000000000000000000011701407603575700256650ustar00rootroot00000000000000$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'lib')) require 'helpers/test_helper' test_name "dsl::helpers::host_helpers test configuration validation" do step "Validate hosts configuration" do assert (hosts.size > 1), "dsl::helpers::host_helpers acceptance tests require at least two hosts" agents = select_hosts(:roles => "agent") assert (agents.size > 1), "dsl::helpers::host_helpers acceptance tests require at least two hosts with the :agent role" assert default, "dsl::helpers::host_helpers acceptance tests require a default host" end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/hocon_helpers_test.rb000066400000000000000000000054071407603575700256550ustar00rootroot00000000000000require 'hocon/config_value_factory' test_name 'Hocon Helpers Test' do hocon_filename = 'hocon.conf' step 'setup : create hocon file to play with' do hocon_content = <<-END { setting1 : "value1", setting2 : 2, setting3 : False } END create_remote_file(hosts, hocon_filename, hocon_content) end step '#hocon_file_read : reads doc' do doc = hocon_file_read_on(hosts[0], hocon_filename) assert(doc.has_value?('setting2')) end step '#hocon_file_edit_on : set_value and verify it exists' do hocon_file_edit_on(hosts, hocon_filename) do |host, doc| doc2 = doc.set_value('c', '[4, 5]') assert(doc2.has_value?('c'), 'Should have inserted "c" value!') end end step '#hocon_file_edit_on : testing failure modes' do def test_filename_failure(filename) begin hocon_file_edit_on(hosts, filename) do |_, _| fail('block should not run in failure mode') end fail('execution should not continue in failure mode') rescue ArgumentError => e assert(e.to_s =~ /requires a filename/) else fail('No exception raised in failure mode') end end step 'filename is nil' do test_filename_failure(nil) end step 'filename is empty string' do test_filename_failure('') end step 'no block given' do begin hocon_file_edit_on(hosts, hocon_filename) fail('execution should not continue in failure mode') rescue ArgumentError => e assert(e.to_s =~ /No block was provided/) else fail('No exception raised in failure mode') end end end step '#hocon_file_edit_on : verify saving workflow' do step '#hocon_file_edit_on : set_value and save' do hocon_file_edit_on(hosts, hocon_filename) do |host, doc| doc2 = doc.set_value('a.b', '[1, 2, 3, 4, 5]') create_remote_file(host, hocon_filename, doc2.render) end end step '#hocon_file_edit_on : independently read value to verify save' do hocon_file_edit_on(hosts, hocon_filename) do |host, doc| msg_fail = 'Should have saved "a.b" value inserted in previous step' assert(doc.has_value?('a.b'), msg_fail) end end end step '#hocon_file_edit_in_place_on : verify auto-saving workflow' do step '#hocon_file_edit_in_place_on : set_value and save' do hocon_file_edit_in_place_on(hosts, hocon_filename) do |host, doc| doc.set_value('c.d', '[6, 2, 73, 4, 45]') end end step '#hocon_file_edit_in_place_on : verify save' do hocon_file_edit_on(hosts, hocon_filename) do |host, doc| msg_fail = 'Should have saved "c.d" value inserted in previous step' assert(doc.has_value?('c.d'), msg_fail) end end end endbeaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/000077500000000000000000000000001407603575700241325ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/add_system32_hosts_entry_test.rb000066400000000000000000000040271407603575700324630ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #add_system32_hosts_entry" do confine_block :to, :platform => /windows/ do step "#add_system32_hosts_entry fails when run on a non-powershell platform" do # NOTE: would expect this to be better documented. if default.is_powershell? logger.info "Skipping failure test on powershell platforms..." else assert_raises RuntimeError do add_system32_hosts_entry default, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } end end end step "#add_system32_hosts_entry, when run on a powershell platform, adds a host entry to system32 etc\\hosts" do if default.is_powershell? add_system32_hosts_entry default, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } # TODO: how do we assert, via powershell, that the entry was added? # NOTE: see: https://github.com/puppetlabs/beaker/commit/685628f4babebe9cb4663418da6a8ff528dd32da#commitcomment-12957573 else logger.info "Skipping test on non-powershell platforms..." end end step "#add_system32_hosts_entry CURRENTLY fails with a TypeError when given a hosts array" do # NOTE: would expect this to fail with Beaker::Host::CommandFailure assert_raises NoMethodError do add_system32_hosts_entry hosts, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } end end end confine_block :except, :platform => /windows/ do step "#add_system32_hosts_entry CURRENTLY fails with RuntimeError when run on a non-windows platform" do # NOTE: would expect this to behave the same way it does on a windows # non-powershell platform (raises Beaker::Host::CommandFailure), or # as requested in the original PR: # https://github.com/puppetlabs/beaker/pull/420/files#r17990622 assert_raises RuntimeError do add_system32_hosts_entry default, { :ip => '123.45.67.89', :name => 'beaker.puppetlabs.com' } end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/archive_file_from_test.rb000066400000000000000000000033341407603575700311640ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #archive_file_from" do step "archiveroot parameter defaults to `archive/sut-files`" do # Create a remote file to archive filepath = default.tmpfile('archive-file-test') create_remote_file(default, filepath, 'contents ignored') # Prepare cleanup so we don't pollute the local filesystem teardown do FileUtils.rm_rf('archive') if Dir.exists?('archive') end # Test that the archiveroot default directory is created assert_equal(false, Dir.exists?('archive')) assert_equal(false, Dir.exists?('archive/sut-files')) archive_file_from(default, filepath) assert_equal(true, Dir.exists?('archive/sut-files')) end step "fails archive_file_from when from_path is non-existant" do filepath = "foo-filepath-should-not-exist" assert_raises IOError do archive_file_from(default, filepath) end end step "archive is copied to local // directory" do # Create a remote file to archive filepath = default.tmpfile('archive-file-test') create_remote_file(default, filepath, 'number of the beast') assert_equal(false, Dir.exists?(filepath)) # Test that the file is copied locally to // Dir.mktmpdir do |tmpdir| tar_path = File.join(tmpdir, default, filepath + '.tgz') archive_file_from(default, filepath, {}, tmpdir, tar_path) assert(File.exists?(tar_path)) expected_path = File.join(tmpdir, default) tgz = Zlib::GzipReader.new(File.open(tar_path, 'rb')) Minitar.unpack(tgz, expected_path) assert_equal('number of the beast', File.read(expected_path + '/' + filepath).strip) end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/backup_the_file_test.rb000066400000000000000000000055041407603575700306260ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #backup_the_file" do step "#backup_the_file CURRENTLY will return nil if the file does not exist in the source directory" do # NOTE: would expect this to fail with Beaker::Host::CommandFailure remote_source = default.tmpdir() remote_destination = default.tmpdir() result = backup_the_file default, remote_source, remote_destination assert_nil result end step "#backup_the_file will fail if the destination directory does not exist" do remote_source = default.tmpdir() remote_source_filename = File.join(remote_source, "puppet.conf") remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_source, "puppet.conf") assert_raises Beaker::Host::CommandFailure do result = backup_the_file default, remote_source, "/non/existent/" end end step "#backup_the_file copies `puppet.conf` from the source to the destination directory" do remote_source = default.tmpdir() remote_source_filename = File.join(remote_source, "puppet.conf") remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_source, "puppet.conf") remote_destination = default.tmpdir() remote_destination_filename = File.join(remote_destination, "puppet.conf.bak") result = backup_the_file default, remote_source, remote_destination assert_equal remote_destination_filename, result remote_contents = on(default, "cat #{remote_destination_filename}").stdout assert_equal contents, remote_contents end step "#backup_the_file copies a named file from the source to the destination directory" do remote_source = default.tmpdir() remote_source_filename = File.join(remote_source, "testfile.txt") remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_source, "testfile.txt") remote_destination = default.tmpdir() remote_destination_filename = File.join(remote_destination, "testfile.txt.bak") result = backup_the_file default, remote_source, remote_destination, "testfile.txt" assert_equal remote_destination_filename, result remote_contents = on(default, "cat #{remote_destination_filename}").stdout assert_equal contents, remote_contents end step "#backup_the_file CURRENTLY will fail if given a hosts array" do remote_source = default.tmpdir() remote_source_filename = File.join(remote_source, "testfile.txt") remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_source, "testfile.txt") remote_destination = default.tmpdir() remote_destination_filename = File.join(remote_destination, "testfile.txt.bak") assert_raises NoMethodError do result = backup_the_file hosts, remote_source, remote_destination end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/check_for_package_test.rb000066400000000000000000000060711407603575700311200ustar00rootroot00000000000000require "helpers/test_helper" # Return the name of a platform-specific package known to be installed on a system def known_installed_package case default['platform'] when /solaris.*11/ "shell/bash" when /solaris.*10/ "SUNWbash" when /windows/ "bash" else "rsync" end end test_name "dsl::helpers::host_helpers #check_for_package" do # NOTE: there does not appear to be a way to confine just to cygwin hosts confine_block :to, :platform => /windows/ do # NOTE: check_for_package on windows currently fails as follows: # # ArgumentError: wrong number of arguments (3 for 1..2) # # Would expect this to be documented better, and to fail with Beaker::Host::CommandFailure step "#check_for_package will return false if the specified package is not installed on the remote host" do result = check_for_package default, "non-existent-package-name" assert !result end step "#check_for_package will return true if the specified package is installed on the remote host" do result = check_for_package default, known_installed_package assert result end step "#check_for_package CURRENTLY fails if given a host array" do assert_raises NoMethodError do check_for_package hosts, known_installed_package end end end confine_block :to, :platform => /solaris/ do step "#check_for_package will return false if the specified package is not installed on the remote host" do result = check_for_package default, "non-existent-package-name" assert !result end step "#check_for_package will return true if the specified package is installed on the remote host" do result = check_for_package default, known_installed_package assert result end step "#check_for_package CURRENTLY fails if given a host array" do assert_raises NoMethodError do check_for_package hosts, known_installed_package end end end confine_block :to, :platform => /osx/ do step "#check_for_package CURRENTLY fails with a RuntimeError on OS X" do assert_raises RuntimeError do check_for_package default, known_installed_package end end end confine_block :except, :platform => /windows|solaris|osx/ do step "#check_for_package will return false if the specified package is not installed on the remote host" do result = check_for_package default, "non-existent-package-name" assert !result end step "#check_for_package will return true if the specified package is installed on the remote host" do install_package default, known_installed_package result = check_for_package default, known_installed_package assert result end step "#check_for_package CURRENTLY fails if given a host array" do # NOTE: would expect this to work across hosts, or to be better # documented. If not supported, should raise # Beaker::Host::CommandFailure assert_raises NoMethodError do check_for_package hosts, known_installed_package end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/create_remote_file_test.rb000066400000000000000000000207251407603575700313410ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #create_remote_file" do def create_remote_file_with_backups host, remote_filename, contents, opts={} result = nil repeat_fibonacci_style_for(10) do begin result = create_remote_file( host, remote_filename, contents, opts ) # return of block is whether or not we're done repeating if result.is_a?(Rsync::Result) || result.is_a?(Beaker::Result) return result.success? end result.each do |individual_result| next if individual_result.success? return false end true rescue Beaker::Host::CommandFailure => err logger.info("create_remote_file threw command failure, details: ") logger.info(" #{err}") logger.info("continuing back-off execution") false end end result end if test_scp_error_on_close? step "#create_remote_file fails when the remote path does not exist" do assert_raises Beaker::Host::CommandFailure do create_remote_file default, "/non/existent/testfile.txt", "contents\n" end end step "#create_remote_file fails when the remote path does not exist, using scp" do assert_raises Beaker::Host::CommandFailure do create_remote_file default, "/non/existent/testfile.txt", "contents\n", { :protocol => 'scp' } end end end step "#create_remote_file creates a remote file with the specified contents" do remote_tmpdir = default.tmpdir("beaker") remote_filename = File.join(remote_tmpdir, "testfile.txt") contents = fixture_contents("simple_text_file") create_remote_file_with_backups default, remote_filename, contents remote_contents = on(default, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end step "#create_remote_file creates a remote file with the specified contents, using scp" do remote_tmpdir = default.tmpdir("beaker") remote_filename = File.join(remote_tmpdir, "testfile.txt") contents = fixture_contents("simple_text_file") create_remote_file_with_backups default, remote_filename, contents, { :protocol => "scp" } remote_contents = on(default, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end step "#create_remote_file creates remote files on all remote hosts, when given an array" do remote_dir = "/tmp/beaker_remote_file_test" # ensure remote dir exists on all hosts hosts.each do |host| # we can't use tmpdir here, because some hosts may have different tmpdir behavior. host.mkdir_p(remote_dir) end remote_filename = File.join(remote_dir, "testfile.txt") contents = fixture_contents("simple_text_file") create_remote_file_with_backups hosts, remote_filename, contents hosts.each do |host| remote_contents = on(host, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end step "#create_remote_file creates remote files on all remote hosts, when given an array, using scp" do remote_dir = "/tmp/beaker_remote_file_test" # ensure remote dir exists on all hosts hosts.each do |host| # we can't use tmpdir here, because some hosts may have different tmpdir behavior. host.mkdir_p(remote_dir) end remote_filename = File.join(remote_dir, "testfile.txt") contents = fixture_contents("simple_text_file") create_remote_file_with_backups hosts, remote_filename, contents, { :protocol => 'scp' } hosts.each do |host| remote_contents = on(host, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end confine_block :except, :platform => /windows/ do # these tests exercise the rsync backend # NOTE: rsync works fine on Windows as long as you use POSIX-style paths. # However, these tests use Host#tmpdir which outputs mixed-style paths # e.g. C:/cygwin64/tmp/beaker.Rp9G6L - Fix me with BKR-1503 confine_block :except, :platform => /osx/ do # packages are unsupported on OSX step "installing rsync on hosts if needed" do hosts.each do |host| if host.check_for_package('rsync') host[:rsync_installed] = true else host[:rsync_installed] = false host.install_package "rsync" end end end end step "#create_remote_file fails and does not create a remote file when the remote path does not exist, using rsync" do assert_raises Beaker::Host::CommandFailure do create_remote_file default, "/non/existent/testfile.txt", "contents\n", { :protocol => 'rsync' } end assert_raises Beaker::Host::CommandFailure do on(default, "cat /non/existent/testfile.txt").exit_code end end step "#create_remote_file creates a remote file with the specified contents, using rsync" do remote_tmpdir = default.tmpdir("beaker") remote_filename = File.join(remote_tmpdir, "testfile.txt") contents = fixture_contents("simple_text_file") create_remote_file_with_backups( default, remote_filename, contents, { :protocol => "rsync" } ) fails_intermittently("https://tickets.puppetlabs.com/browse/BKR-612", "default" => default, "remote_tmpdir" => remote_tmpdir, "remote_filename" => remote_filename, "contents" => contents, "result" => result ) do remote_contents = on(default, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end step "#create_remote_file creates remote files on all remote hosts, when given an array, using rsync" do remote_tmpdir = "/tmp/beaker_remote_file_test" # ensure remote dir exists on all hosts hosts.each do |host| # we can't use tmpdir here, because some hosts may have different tmpdir behavior. host.mkdir_p(remote_tmpdir) end remote_filename = File.join(remote_tmpdir, "testfile.txt") contents = fixture_contents("simple_text_file") result = create_remote_file_with_backups( hosts, remote_filename, contents, { :protocol => 'rsync' } ) hosts.each do |host| fails_intermittently("https://tickets.puppetlabs.com/browse/BKR-612", "host" => host, "remote_tmpdir" => remote_tmpdir, "remote_filename" => remote_filename, "contents" => contents, "result" => result ) do remote_contents = on(host, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end end confine_block :except, :platform => /osx/ do # packages are unsupported on OSX step "uninstalling rsync on hosts if needed" do hosts.each do |host| if !host[:rsync_installed] # rsync wasn't installed on #{host} when we started, so we should clean up after ourselves rsync_package = "rsync" # solaris-10 uses OpenCSW pkgutil, which prepends "CSW" to its provided packages # TODO: fix this with BKR-1502 rsync_package = "CSWrsync" if host['platform'] =~ /solaris-10/ host.uninstall_package rsync_package end host.delete(:rsync_installed) end end end end step "#create_remote_file' does not create a remote file when an unknown protocol is specified" do remote_tmpdir = default.tmpdir("beaker") remote_filename = File.join(remote_tmpdir, "testfile.txt") contents = fixture_contents("simple_text_file") create_remote_file default, remote_filename, contents, { :protocol => 'unknown' } assert_raises Beaker::Host::CommandFailure do on(default, "cat #{remote_filename}").exit_code end end # NOTE: there does not appear to be a way to confine just to cygwin hosts confine_block :to, :platform => /windows/ do # NOTE: rsync works fine on Windows as long as you use POSIX-style paths. # Fix me with BKR-1503 step "#create_remote_file CURRENTLY fails on #{default['platform']}, using rsync" do remote_tmpdir = default.tmpdir("beaker") remote_filename = File.join(remote_tmpdir, "testfile.txt") contents = fixture_contents("simple_text_file") assert_raises Beaker::Host::CommandFailure do create_remote_file default, remote_filename, contents, { :protocol => "rsync" } end assert_raises Beaker::Host::CommandFailure do remote_contents = on(default, "cat #{remote_filename}").stdout end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/curl_on_test.rb000066400000000000000000000033251407603575700271620ustar00rootroot00000000000000require "helpers/test_helper" # construct an appropriate local file URL for curl testing def host_local_url(host, path) if host.is_cygwin? path_result = on(host, "cygpath #{path}") path = path_result.raw_output.chomp end "file://#{path}" end test_name "dsl::helpers::host_helpers #curl_on" do step "#curl_on fails if the URL in question cannot be reached" do assert Beaker::Host::CommandFailure do curl_on default, "file:///non/existent.html" end end step "#curl_on can retrieve the contents of a URL, using standard curl options" do remote_tmpdir = default.tmpdir() remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_tmpdir, "testfile.txt") remote_targetfilename = File.join remote_tmpdir, "outfile.txt" result = curl_on default, "-o #{remote_targetfilename} #{host_local_url default, remote_filename}" assert_equal 0, result.exit_code remote_contents = on(default, "cat #{remote_targetfilename}").stdout assert_equal contents, remote_contents end step "#curl_on can retrieve the contents of a URL, when given a hosts array" do remote_tmpdir = default.tmpdir() on hosts, "mkdir -p #{remote_tmpdir}" remote_filename = contents = nil hosts.each do |host| remote_filename, contents = create_remote_file_from_fixture("simple_text_file", host, remote_tmpdir, "testfile.txt") end remote_targetfilename = File.join remote_tmpdir, "outfile.txt" result = curl_on hosts, "-o #{remote_targetfilename} #{host_local_url default, remote_filename}" hosts.each do |host| remote_contents = on(host, "cat #{remote_targetfilename}").stdout assert_equal contents, remote_contents end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/curl_with_retries_test.rb000066400000000000000000000017761407603575700312660ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #curl_with_retries" do step "#curl_with_retries CURRENTLY fails with a RuntimeError if retries are exhausted without fetching the specified URL" do # NOTE: would expect that this would raise Beaker::Host::CommandFailure assert_raises RuntimeError do curl_with_retries \ "description", default, "file:///non/existent.html", desired_exit_codes = [0], max_retries = 2, retry_interval = 0.01 end end step "#curl_with_retries retrieves the contents of a URL after retrying" do # TODO: testing curl_with_retries relies on having a portable means of # making an unavailable URL available after a period of time. end step "#curl_with_retries can retrieve the contents of a URL after retrying, when given a hosts array" do # TODO: testing curl_with_retries relies on having a portable means of # making an unavailable URL available after a period of time. end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/deploy_package_repo_test.rb000066400000000000000000000121321407603575700315110ustar00rootroot00000000000000require "helpers/test_helper" require "fileutils" test_name "dsl::helpers::host_helpers #deploy_package_repo" do confine_block :to, :platform => /^el-4/ do step "#deploy_package_repo CURRENTLY does nothing and throws no error on the #{default['platform']} platform" do # NOTE: would expect this to fail with Beaker::Host::CommandFailure Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" platform = default['platform'] local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "pl-#{name}-#{version}-repos-pe-#{platform}.repo") assert_nil deploy_package_repo(default, local_dir, name, version) end end end confine_block :to, :platform => /fedora|centos|eos|el-[56789]/i do step "#deploy_package_repo pushes repo package to /etc/yum.repos.d on the remote host" do Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" platform = default['platform'] FileUtils.mkdir(File.join(local_dir, "rpm")) local_filename, contents = create_local_file_from_fixture("simple_text_file", File.join(local_dir, "rpm"), "pl-#{name}-#{version}-repos-pe-#{platform}.repo") deploy_package_repo default, local_dir, name, version remote_contents = on(default, "cat /etc/yum.repos.d/#{name}.repo").stdout assert_equal contents, remote_contents # teardown on default, "rm /etc/yum.repos.d/#{name}.repo" end end step "#deploy_package_repo CURRENTLY fails with NoMethodError when passed a hosts array" do # NOTE: would expect this to handle host arrays, or raise Beaker::Host::CommandFailure Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" platform = default['platform'] local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "pl-#{name}-#{version}-repos-pe-#{platform}.repo") assert_raises NoMethodError do deploy_package_repo hosts, local_dir, name, version end end end end confine_block :to, :platform => /ubuntu|debian|cumulus/i do step "#deploy_package_repo pushes repo package to /etc/apt/sources.list.d on the remote host" do Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" codename = default['platform'].codename FileUtils.mkdir(File.join(local_dir, "deb")) local_filename, contents = create_local_file_from_fixture("simple_text_file", File.join(local_dir, "deb"), "pl-#{name}-#{version}-#{codename}.list") deploy_package_repo default, local_dir, name, version remote_contents = on(default, "cat /etc/apt/sources.list.d/#{name}.list").stdout assert_equal contents, remote_contents # teardown on default, "rm /etc/apt/sources.list.d/#{name}.list" end end step "#deploy_package_repo CURRENTLY fails with NoMethodError when passed a hosts array" do # NOTE: would expect this to handle host arrays, or raise Beaker::Host::CommandFailure Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" codename = default['platform'].codename FileUtils.mkdir(File.join(local_dir, "deb")) local_filename, contents = create_local_file_from_fixture("simple_text_file", File.join(local_dir, "deb"), "pl-#{name}-#{version}-#{codename}.list") assert_raises NoMethodError do deploy_package_repo hosts, local_dir, name, version end end end end confine_block :to, :platform => /opensuse|sles/i do step "#deploy_package_repo updates zypper repository list on the remote host" do Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" platform = default['platform'] FileUtils.mkdir(File.join(local_dir, "rpm")) local_filename, contents = create_local_file_from_fixture("#{default["platform"]}.repo", File.join(local_dir, "rpm"), "pl-#{name}-#{version}-repos-pe-#{platform}.repo") deploy_package_repo default, local_dir, name, version result = on default, "zypper repos -d" assert_match "PE-2016.4-#{default['platform']}", result.stdout # teardown on default, "zypper rr #{default['platform']}" end end end confine_block :except, :platform => /el-\d|fedora|centos|eos|ubuntu|debian|cumulus|opensuse|sles/i do # OS X, windows (cygwin, powershell), solaris, etc. step "#deploy_package_repo CURRENTLY fails with a RuntimeError on on the #{default['platform']} platform" do # NOTE: would expect this to raise Beaker::Host::CommandFailure instead of RuntimeError Dir.mktmpdir do |local_dir| name = "puppet-server" version = "9.9.9" platform = default['platform'] local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "pl-#{name}-#{version}-repos-pe-#{platform}.repo") assert_raises RuntimeError do deploy_package_repo default, local_dir, name, version end end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/echo_on_test.rb000066400000000000000000000006471407603575700271370ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #echo_on" do step "#echo_on echoes the supplied string on the remote host" do output = echo_on(default, "contents") assert_equal output, "contents" end step "#echo_on echoes the supplied string on all hosts when given a hosts array" do results = echo_on(hosts, "contents") assert_equal ["contents"] * hosts.size, results end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/install_package_test.rb000066400000000000000000000003171407603575700306400ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #install_package" do step "#install_package is just a wrapper for Host#install_package, see acceptance/tests/base/host/packages.rb" end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/on_test.rb000066400000000000000000000111141407603575700261300ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #on" do step "#on raises an exception when remote command fails" do assert_raises(Beaker::Host::CommandFailure) do on default, "/bin/nonexistent-command" end end step "#on makes command output available via `.stdout` on success" do output = on(default, %Q{echo "echo via on"}).stdout assert_equal "echo via on\n", output end step "#on makes command error output available via `.stderr` on success" do output = on(default, "/bin/nonexistent-command", :acceptable_exit_codes => [0, 127]).stderr assert_match /No such file/, output end step "#on makes exit status available via `.exit_code`" do status = on(default, %Q{echo "echo via on"}).exit_code assert_equal 0, status end step "#on with :acceptable_exit_codes will not fail for named exit codes" do result = on default, "/bin/nonexistent-command", :acceptable_exit_codes => [0, 127] output = result.stderr assert_match /No such file/, output status = result.exit_code assert_equal 127, status end step "#on with :acceptable_exit_codes will fail for other exit codes" do assert_raises(Beaker::Host::CommandFailure) do on default, %Q{echo "echo via on"}, :acceptable_exit_codes => [127] end end step "#on will pass :environment options to the remote host as ENV settings" do result = on default, "env", { :environment => { 'FOO' => 'bar' } } output = result.stdout assert_match /\bFOO=bar\b/, output end step "#on runs command on all hosts when given a host array" do # Run a command which is (basically) guaranteed to have distinct output # on every host, and only requires bash to be present to run on any of # our platforms. results = on hosts, %Q{echo "${RANDOM}:${RANDOM}:${RANDOM}"} # assert that we got results back for every host assert_equal hosts.size, results.size # that they were all successful runs results.each do |result| assert_equal 0, result.exit_code end # and that we have |hosts| distinct outputs unique_outputs = results.map(&:output).uniq assert_equal hosts.size, unique_outputs.size end step "#on runs command on all hosts matching a role, when given a symbol" do # Run a command which is (basically) guaranteed to have distinct output # on every host, and only requires bash to be present to run on any of # our platforms. results = on :agent, %Q{echo "${RANDOM}:${RANDOM}:${RANDOM}"} # assert that we got results back for every host assert_equal hosts.size, results.size # that they were all successful runs results.each do |result| assert_equal 0, result.exit_code end # and that we have |hosts| distinct outputs unique_outputs = results.map(&:output).uniq assert_equal hosts.size, unique_outputs.size end step "#on runs command on all hosts matching a role, when given a string" do # Run a command which is (basically) guaranteed to have distinct output # on every host, and only requires bash to be present to run on any of # our platforms. results = on "agent", %Q{echo "${RANDOM}:${RANDOM}:${RANDOM}"} # assert that we got results back for every host assert_equal hosts.size, results.size # that they were all successful runs results.each do |result| assert_equal 0, result.exit_code end # and that we have |hosts| distinct outputs unique_outputs = results.map(&:output).uniq assert_equal hosts.size, unique_outputs.size end step "#on allows assertions to be used in the optional block" do on hosts, %Q{echo "${RANDOM}:${RANDOM}"} do assert_match /\d+:\d+/, stdout end end step "#on executes in parallel with :run_in_parallel => true" do parent_pid = Process.pid results = on( hosts, %Q{echo "${RANDOM}:${RANDOM}:${RANDOM}"}, :run_in_parallel => true) { assert(Process.pid != parent_pid) } # assert that we got results back for every host assert_equal hosts.size, results.size # that they were all successful runs results.each do |result| assert_equal 0, result.exit_code end # and that we have |hosts| distinct outputs unique_outputs = results.map(&:output).uniq assert_equal hosts.size, unique_outputs.size end step "#on in parallel exits after all processes complete if an exception is raised in one process" do start = Time.now tmp = nil assert_raises NoMethodError do on( hosts, %Q{echo "blah"}, :run_in_parallel => true) { sleep(1) tmp.blah } end assert(Time.now > start + 1) end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/retry_on_test.rb000066400000000000000000000034221407603575700273600ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #retry_on" do step "#retry_on CURRENTLY fails with a RuntimeError if command does not pass after all retries" do # NOTE: would have expected this to fail with Beaker::Hosts::CommandFailure remote_tmpdir = default.tmpdir() remote_script_file = File.join(remote_tmpdir, "test.sh") remote_filename, contents = create_remote_file_from_fixture("retry_script", default, remote_tmpdir, "test.sh") assert_raises RuntimeError do retry_on default, "bash #{remote_script_file} #{remote_tmpdir} 10", { :max_retries => 2, :retry_interval => 0.1 } end end step "#retry_on succeeds if command passes before retries are exhausted" do remote_tmpdir = default.tmpdir() remote_script_file = File.join(remote_tmpdir, "test.sh") remote_filename, contents = create_remote_file_from_fixture("retry_script", default, remote_tmpdir, "test.sh") result = retry_on default, "bash #{remote_script_file} #{remote_tmpdir} 2", { :max_retries => 4, :retry_interval => 0.1 } assert_equal 0, result.exit_code assert_equal "", result.stdout end step "#retry_on CURRENTLY fails when provided a host array" do # NOTE: would expect this to work across hosts, or be better documented and # to raise Beaker::Host::CommandFailure remote_tmpdir = default.tmpdir() remote_script_file = File.join(remote_tmpdir, "test.sh") hosts.each do |host| on host, "mkdir -p #{remote_tmpdir}" remote_filename, contents = create_remote_file_from_fixture("retry_script", host, remote_tmpdir, "test.sh") end assert_raises NoMethodError do result = retry_on hosts, "bash #{remote_script_file} #{remote_tmpdir} 2", { :max_retries => 4, :retry_interval => 0.1 } end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/rsync_to_test.rb000066400000000000000000000154771407603575700273740ustar00rootroot00000000000000require "helpers/test_helper" require "rsync" test_name "dsl::helpers::host_helpers #rsync_to" do def rsync_to_with_backups hosts, local_filename, remote_dir result = nil repeat_fibonacci_style_for(10) do begin result = rsync_to( hosts, local_filename, remote_dir ) # return of block is whether or not we're done repeating return result.success? if result.is_a? Rsync::Result result.each do |individual_result| next if individual_result.success? return false end true rescue Beaker::Host::CommandFailure => err logger.info("create_remote_file threw command failure, details: ") logger.info(" #{err}") logger.info("continuing back-off execution") false end end result end # NOTE: there does not seem to be a reliable way to confine to cygwin hosts. confine_block :to, :platform => /windows/ do # NOTE: rsync works fine on Windows as long as you use POSIX-style paths. # However, these tests use Host#tmpdir which outputs mixed-style paths # e.g. C:/cygwin64/tmp/beaker.Rp9G6L - Fix me with BKR-1503 step "#rsync_to CURRENTLY fails on windows systems" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") remote_tmpdir = default.tmpdir() assert_raises Beaker::Host::CommandFailure do rsync_to default, local_filename, remote_tmpdir end remote_filename = File.join(remote_tmpdir, "testfile.txt") assert_raises Beaker::Host::CommandFailure do remote_contents = on(default, "cat #{remote_filename}").stdout end end end end confine_block :except, :platform => /windows/ do # NOTE: rsync works fine on Windows as long as you use POSIX-style paths. # However, these tests use Host#tmpdir which outputs mixed-style paths # e.g. C:/cygwin64/tmp/beaker.Rp9G6L - Fix me with BKR-1503 step "#rsync_to fails if the local file cannot be found" do remote_tmpdir = default.tmpdir() assert_raises IOError do rsync_to default, "/non/existent/file.txt", remote_tmpdir end end confine_block :except, :platform => /osx/ do # packages are unsupported on OSX step "uninstalling rsync on hosts if needed" do hosts.each do |host| if host.check_for_package('rsync') host[:rsync_installed] = true rsync_package = "rsync" # solaris-10 uses OpenCSW pkgutil, which prepends "CSW" to its provided packages # TODO: fix this with BKR-1502 rsync_package = "CSWrsync" if host['platform'] =~ /solaris-10/ host.uninstall_package rsync_package else host[:rsync_installed] = false end end end # rsync is preinstalled on OSX step "#rsync_to fails if rsync is not installed on the remote host" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") hosts.each do |host| remote_tmpdir = host.tmpdir("beaker") remote_filename = File.join(remote_tmpdir, "testfile.txt") assert_raises Beaker::Host::CommandFailure do rsync_to default, local_filename, remote_tmpdir end end end end step "installing rsync on hosts" do hosts.each do |host| host.install_package "rsync" end end end step "#rsync_to fails if the remote path cannot be found" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") assert_raises Beaker::Host::CommandFailure do rsync_to default, local_filename, "/non/existent/testfile.txt" end assert_raises Beaker::Host::CommandFailure do on(default, "cat /non/existent/testfile.txt").exit_code end end end step "#rsync_to creates the file on the remote system" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") remote_tmpdir = default.tmpdir() remote_filename = File.join(remote_tmpdir, "testfile.txt") result = rsync_to_with_backups default, local_filename, remote_tmpdir fails_intermittently("https://tickets.puppetlabs.com/browse/QENG-3053", "result" => result, "default" => default, "contents" => contents, "local_filename" => local_filename, "local_dir" => local_dir, "remote_filename" => remote_filename, "remote_tmdir" => remote_tmpdir, "result" => result.inspect, ) do remote_contents = on(default, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end end step "#rsync_to creates the file on all remote systems when a host array is provided" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") remote_tmpdir = default.tmpdir() on hosts, "mkdir -p #{remote_tmpdir}" remote_filename = File.join(remote_tmpdir, "testfile.txt") result = rsync_to_with_backups(hosts, local_filename, remote_tmpdir) hosts.each do |host| fails_intermittently("https://tickets.puppetlabs.com/browse/QENG-3053", "result" => result, "host" => host, "contents" => contents, "local_filename" => local_filename, "local_dir" => local_dir, "remote_filename" => remote_filename, "remote_tmdir" => remote_tmpdir, "result" => result.inspect, ) do remote_contents = on(host, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end end end confine_block :except, :platform => /osx/ do # packages are unsupported on OSX step "uninstalling rsync on hosts if needed" do hosts.each do |host| if !host[:rsync_installed] # rsync wasn't installed on #{host} when we started, so we should clean up after ourselves rsync_package = "rsync" # solaris-10 uses OpenCSW pkgutil, which prepends "CSW" to its provided packages # TODO: fix this with BKR-1502 rsync_package = "CSWrsync" if host['platform'] =~ /solaris-10/ host.uninstall_package rsync_package end host.delete(:rsync_installed) end end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/run_cron_on_test.rb000066400000000000000000000225571407603575700300520ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #run_cron_on" do confine_block :to, :platform => /windows/ do step "#run_cron_on fails on windows platforms when listing cron jobs for a user on a host" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :list, default['user'] end end end confine_block :to, :platform => /solaris/ do step "#run_cron_on CURRENTLY does nothing and returns `nil` when an unknown command is provided" do # NOTE: would have expected this to raise Beaker::Host::CommandFailure instead assert_nil run_cron_on default, :nonexistent_action, default['user'] end step "#run_cron_on CURRENTLY does not fail when listing cron jobs for an unknown user" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :list, "nonexistentuser" end end step "#run_cron_on CURRENTLY does not fail when listing cron jobs for a user with no cron entries" do result = run_cron_on default, :list, default['user'] assert_equal 0, result.exit_code end step "#run_cron_on returns a list of cron jobs for a user with cron entries" do # this basically requires us to add a cron entry to make this work run_cron_on default, :add, default['user'], "* * * * * /bin/ls >/dev/null" result = run_cron_on default, :list, default['user'] assert_equal 0, result.exit_code assert_match %r{/bin/ls}, result.stdout end step "#run_cron_on CURRENTLY does not fail, but returns nil, when adding cron jobs for an unknown user" do result = run_cron_on default, :add, "nonexistentuser", %Q{* * * * * /bin/echo "hello" >/dev/null} assert_nil result end step "#run_cron_on CURRENTLY does not fail, but returns nil, when attempting to add a bad cron entry" do result = run_cron_on default, :add, default['user'], "* * * * /bin/ls >/dev/null" assert_nil result end step "#run_cron_on can add a cron job for a user on a host" do run_cron_on default, :add, default['user'], %Q{* * * * * /bin/echo "hello" >/dev/null} result = run_cron_on default, :list, default['user'] assert_equal 0, result.exit_code assert_match %r{/bin/echo}, result.stdout end step "#run_cron_on CURRENTLY replaces all of user's cron jobs with any newly added jobs" do # NOTE: would have expected this to append new entries, or manage them as puppet manages # cron entries. See also: https://github.com/puppetlabs/beaker/pull/937#discussion_r38338494 1.upto(3) do |job_number| run_cron_on default, :add, default['user'], %Q{* * * * * /bin/echo "job :#{job_number}:" >/dev/null} end result = run_cron_on default, :list, default['user'] assert_no_match %r{job :1:}, result.stdout assert_no_match %r{job :2:}, result.stdout assert_match %r{job :3:}, result.stdout end step "#run_cron_on :remove CURRENTLY removes all cron jobs for a user on a host" do # NOTE: would have expected a more granular approach to removing cron jobs # for a user on a host. This should otherwise be better documented. run_cron_on default, :add, default['user'], %Q{* * * * * /bin/echo "quality: job 1" >/dev/null} result = run_cron_on default, :list, default['user'] assert_match %r{quality: job 1}, result.stdout run_cron_on default, :remove, default['user'] assert_raises Beaker::Host::CommandFailure do run_cron_on default, :list, default['user'] end end step "#run_cron_on fails when removing cron jobs for an unknown user" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :remove, "nonexistentuser" end end step "#run_cron_on can list cron jobs for a user on all hosts when given a host array" do hosts.each do |host| # this basically requires us to add a cron entry to make this work run_cron_on host, :add, host['user'], "* * * * * /bin/ls >/dev/null" end results = run_cron_on hosts, :list, default['user'] results.each do |result| assert_match %r{/bin/ls}, result.stdout end end step "#run_cron_on can add cron jobs for a user on all hosts when given a host array" do run_cron_on hosts, :add, default['user'], "* * * * * /bin/ls >/dev/null" results = run_cron_on hosts, :list, default['user'] results.each do |result| assert_match %r{/bin/ls}, result.stdout end end step "#run_cron_on can remove cron jobs for a user on all hosts when given a host array" do run_cron_on hosts, :add, default['user'], "* * * * * /bin/ls >/dev/null" run_cron_on hosts, :remove, default['user'] hosts.each do |host| assert_raises Beaker::Host::CommandFailure do results = run_cron_on host, :list, host['user'] end end end end confine_block :except, :platform => /windows|solaris/ do step "#run_cron_on CURRENTLY does nothing and returns `nil` when an unknown command is provided" do # NOTE: would have expected this to raise Beaker::Host::CommandFailure instead assert_nil run_cron_on default, :nonexistent_action, default['user'] end step "#run_cron_on fails when listing cron jobs for an unknown user" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :list, "nonexistentuser" end end step "#run_cron_on fails when listing cron jobs for a user with no cron entries" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :list, default['user'] end end step "#run_cron_on returns a list of cron jobs for a user with cron entries" do # this basically requires us to add a cron entry to make this work run_cron_on default, :add, default['user'], "* * * * * /bin/ls >/dev/null" result = run_cron_on default, :list, default['user'] assert_equal 0, result.exit_code assert_match %r{/bin/ls}, result.stdout end step "#run_cron_on fails when adding cron jobs for an unknown user" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :add, "nonexistentuser", %Q{* * * * * /bin/echo "hello" >/dev/null} end end step "#run_cron_on fails when attempting to add a bad cron entry" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :add, default['user'], "* * * * /bin/ls >/dev/null" end end step "#run_cron_on can add a cron job for a user on a host" do run_cron_on default, :add, default['user'], %Q{* * * * * /bin/echo "hello" >/dev/null} result = run_cron_on default, :list, default['user'] assert_equal 0, result.exit_code assert_match %r{/bin/echo}, result.stdout end step "#run_cron_on CURRENTLY replaces all of user's cron jobs with any newly added jobs" do # NOTE: would have expected this to append new entries, or manage them as puppet manages # cron entries. See also: https://github.com/puppetlabs/beaker/pull/937#discussion_r38338494 1.upto(3) do |job_number| run_cron_on default, :add, default['user'], %Q{* * * * * /bin/echo "job :#{job_number}:" >/dev/null} end result = run_cron_on default, :list, default['user'] assert_no_match %r{job :1:}, result.stdout assert_no_match %r{job :2:}, result.stdout assert_match %r{job :3:}, result.stdout end step "#run_cron_on :remove CURRENTLY removes all cron jobs for a user on a host" do # NOTE: would have expected a more granular approach to removing cron jobs # for a user on a host. This should otherwise be better documented. run_cron_on default, :add, default['user'], %Q{* * * * * /bin/echo "quality: job 1" >/dev/null} result = run_cron_on default, :list, default['user'] assert_match %r{quality: job 1}, result.stdout run_cron_on default, :remove, default['user'] assert_raises Beaker::Host::CommandFailure do run_cron_on default, :list, default['user'] end end step "#run_cron_on fails when removing cron jobs for an unknown user" do assert_raises Beaker::Host::CommandFailure do run_cron_on default, :remove, "nonexistentuser" end end step "#run_cron_on can list cron jobs for a user on all hosts when given a host array" do hosts.each do |host| # this basically requires us to add a cron entry to make this work run_cron_on host, :add, host['user'], "* * * * * /bin/ls >/dev/null" end results = run_cron_on hosts, :list, default['user'] results.each do |result| assert_match %r{/bin/ls}, result.stdout end end step "#run_cron_on can add cron jobs for a user on all hosts when given a host array" do run_cron_on hosts, :add, default['user'], "* * * * * /bin/ls >/dev/null" results = run_cron_on hosts, :list, default['user'] results.each do |result| assert_match %r{/bin/ls}, result.stdout end end step "#run_cron_on can remove cron jobs for a user on all hosts when given a host array" do run_cron_on hosts, :add, default['user'], "* * * * * /bin/ls >/dev/null" run_cron_on hosts, :remove, default['user'] hosts.each do |host| assert_raises Beaker::Host::CommandFailure do results = run_cron_on host, :list, host['user'] end end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/run_script_on_test.rb000066400000000000000000000047561407603575700304160ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #run_script_on" do step "#run_script_on fails when the local script cannot be found" do assert_raises IOError do run_script_on default, "/non/existent/testfile.sh" end end step "#run_script_on fails when there is an error running the remote script" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") assert_raises Beaker::Host::CommandFailure do run_script_on default, local_filename end end end step "#run_script_on passes along options when running the remote command" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") result = run_script_on default, local_filename, { :accept_all_exit_codes => true } assert_equal 1, result.exit_code end end step "#run_script_on runs the script on the remote host" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") results = run_script_on default, local_filename assert_equal 0, results.exit_code assert_equal "output\n", results.stdout end end step "#run_script_on allows assertions in an optional block" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") results = run_script_on default, local_filename do assert_equal 0, exit_code assert_equal "output\n", stdout end end end step "#run_script_on runs the script on all remote hosts when a host array is provided" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") results = run_script_on hosts, local_filename assert_equal hosts.size, results.size results.each do |result| assert_equal 0, result.exit_code assert_equal "output\n", result.stdout end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/run_script_test.rb000066400000000000000000000035701407603575700277130ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #run_script" do step "#run_script fails when the local script cannot be found" do assert_raises IOError do run_script "/non/existent/testfile.sh" end end step "#run_script fails when there is an error running the remote script" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") assert_raises Beaker::Host::CommandFailure do run_script local_filename end end end step "#run_script passes along options when running the remote command" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("failing_shell_script", local_dir, "testfile.sh", "a+x") result = run_script local_filename, { :accept_all_exit_codes => true } assert_equal 1, result.exit_code end end step "#run_script runs the script on the remote host" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") results = run_script local_filename assert_equal 0, results.exit_code assert_equal "output\n", results.stdout end end step "#run_script allows assertions in an optional block" do Dir.mktmpdir do |local_dir| local_filename = File.join(local_dir, "testfile.sh") local_filename, contents = create_local_file_from_fixture("shell_script_with_output", local_dir, "testfile.sh", "a+x") results = run_script local_filename do assert_equal 0, exit_code assert_equal "output\n", stdout end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/scp_from_test.rb000066400000000000000000000037161407603575700273350ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #scp_from" do if test_scp_error_on_close? step "#scp_from fails if the local path cannot be found" do remote_tmpdir = default.tmpdir() remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_tmpdir, "testfile.txt") assert_raises Beaker::Host::CommandFailure do scp_from default, remote_filename, "/non/existent/file.txt" end end step "#scp_from fails if the remote file cannot be found" do Dir.mktmpdir do |local_dir| assert_raises Beaker::Host::CommandFailure do scp_from default, "/non/existent/remote/file.txt", local_dir end end end end step "#scp_from creates the file on the local system" do Dir.mktmpdir do |local_dir| remote_tmpdir = default.tmpdir() remote_filename, contents = create_remote_file_from_fixture("simple_text_file", default, remote_tmpdir, "testfile.txt") scp_from default, remote_filename, local_dir local_filename = File.join(local_dir, "testfile.txt") assert_equal contents, File.read(local_filename) end end step "#scp_from CURRENTLY creates and repeatedly overwrites the file on the local system when given a hosts array" do # NOTE: expect this behavior to be well-documented, or for overwriting a # file repeatedly to generate an error Dir.mktmpdir do |local_dir| remote_tmpdir = default.tmpdir() remote_filename = File.join(remote_tmpdir, "testfile.txt") on hosts, "mkdir -p #{remote_tmpdir}" results = on hosts, %Q{echo "${RANDOM}:${RANDOM}:${RANDOM}" > #{remote_filename}} scp_from hosts, remote_filename, local_dir remote_contents = on(hosts.last, "cat #{remote_filename}").stdout local_filename = File.join(local_dir, "testfile.txt") local_contents = File.read(local_filename) assert_equal remote_contents, local_contents end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/scp_to_test.rb000066400000000000000000000034361407603575700270130ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #scp_to" do step "#scp_to fails if the local file cannot be found" do remote_tmpdir = default.tmpdir() assert_raises IOError do scp_to default, "/non/existent/file.txt", remote_tmpdir end end if test_scp_error_on_close? step "#scp_to fails if the remote path cannot be found" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") # assert_raises Beaker::Host::CommandFailure do assert_raises RuntimeError do scp_to default, local_filename, "/non/existent/remote/file.txt" end end end end step "#scp_to creates the file on the remote system" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") remote_tmpdir = default.tmpdir() scp_to default, local_filename, remote_tmpdir remote_filename = File.join(remote_tmpdir, "testfile.txt") remote_contents = on(default, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end step "#scp_to creates the file on all remote systems when a host array is provided" do Dir.mktmpdir do |local_dir| local_filename, contents = create_local_file_from_fixture("simple_text_file", local_dir, "testfile.txt") remote_tmpdir = default.tmpdir() on hosts, "mkdir -p #{remote_tmpdir}" remote_filename = File.join(remote_tmpdir, "testfile.txt") scp_to hosts, local_filename, remote_tmpdir hosts.each do |host| remote_contents = on(host, "cat #{remote_filename}").stdout assert_equal contents, remote_contents end end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/shell_test.rb000066400000000000000000000032361407603575700266310ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #shell" do step "#shell raises an exception when remote command fails" do assert_raises(Beaker::Host::CommandFailure) do shell "/bin/nonexistent-command" end end step "#shell makes command output available via `.stdout` on success" do output = shell(%Q{echo "echo via on"}).stdout assert_equal "echo via on\n", output end step "#shell makes command error output available via `.stderr` on success" do output = shell("/bin/nonexistent-command", :acceptable_exit_codes => [0, 127]).stderr assert_match /No such file/, output end step "#shell makes exit status available via `.exit_code`" do status = shell(%Q{echo "echo via on"}).exit_code assert_equal 0, status end step "#shell with :acceptable_exit_codes will not fail for named exit codes" do result = shell "/bin/nonexistent-command", :acceptable_exit_codes => [0, 127] output = result.stderr assert_match /No such file/, output status = result.exit_code assert_equal 127, status end step "#shell with :acceptable_exit_codes will fail for other exit codes" do assert_raises(Beaker::Host::CommandFailure) do shell %Q{echo "echo via on"}, :acceptable_exit_codes => [127] end end step "#shell will pass :environment options to the remote host as ENV settings" do result = shell "env", { :environment => { 'FOO' => 'bar' } } output = result.stdout assert_match /\bFOO=bar\b/, output end step "#shell allows assertions to be used in the optional block" do shell %Q{echo "${RANDOM}:${RANDOM}"} do assert_match /\d+:\d+/, stdout end end end beaker-4.30.0/acceptance/tests/base/dsl/helpers/host_helpers/upgrade_package_test.rb000066400000000000000000000053711407603575700306260ustar00rootroot00000000000000require "helpers/test_helper" test_name "dsl::helpers::host_helpers #upgrade_package" do # NOTE: there does not appear to be a way to confine just to cygwin hosts confine_block :to, :platform => /windows/ do # NOTE: check_for_package on windows currently fails as follows: # # ArgumentError: wrong number of arguments (3 for 1..2) # # Would expect this to be documented better, and to fail with Beaker::Host::CommandFailure step "#upgrade_package CURRENTLY fails on windows platforms with a RuntimeError" do # NOTE: this is not a supported platform but would expect a Beaker::Host::CommandFailure assert_raises RuntimeError do upgrade_package default, "bash" end end end confine_block :to, :platform => /osx/ do step "#upgrade_package CURRENTLY fails with a RuntimeError on OS X" do # NOTE: documentation could be better on this method assert_raises RuntimeError do upgrade_package default, "bash" end end end confine_block :except, :platform => /windows|osx/ do confine_block :to, :platform => /centos|el-\d/ do step "#upgrade_package CURRENTLY does not fail on CentOS if unknown package is specified" do # NOTE: I would expect this to fail with an Beaker::Host::CommandFailure, # but maybe it's because yum doesn't really care: # # > Loaded plugins: fastestmirror # > Loading mirror speeds from cached hostfile # > Setting up Update Process # > No package non-existent-package-name available. # > No Packages marked for Update result = upgrade_package default, "non-existent-package-name" assert_match /No Packages marked for Update/i, result end end confine_block :except, :platform => /centos|el-\d/ do step "#upgrade_package fails if package is not already installed" do assert_raises Beaker::Host::CommandFailure do upgrade_package default, "non-existent-package-name" end end end step "#upgrade_package succeeds if package is installed" do # TODO: anyone have any bright ideas on how to portably install an old # version of a package, to really test an upgrade? install_package default, "rsync" upgrade_package default, "rsync" assert check_for_package(default, "rsync"), "package was not successfully installed/upgraded" end step "#upgrade_package CURRENTLY fails when given a host array" do # NOTE: would expect this to work across hosts, or to be better documented, # if not support, should raise Beaker::Host::CommandFailure assert_raises NoMethodError do upgrade_package hosts, "rsync" end end end end beaker-4.30.0/acceptance/tests/base/dsl/platform_tag_confiner_test.rb000066400000000000000000000035631407603575700257260ustar00rootroot00000000000000test_name "DSL::Structure::PlatformTagConfiner" do pstc_method_name = "#platform_specific_tag_confines" step "#{pstc_method_name} doesn't change hosts if there are no tags" do previous_hosts = hosts.dup platform_specific_tag_confines assert_equal previous_hosts, hosts, "#{pstc_method_name} changed the hosts array" # cleanup options[:platform_tag_confines_object] = nil options[:platform_tag_confines] = nil @hosts = previous_hosts end step "#{pstc_method_name} can remove hosts from a test, or be skipped if empty" do assert hosts.length() > 0, "#{pstc_method_name} did not have enough hosts to test" previous_hosts = hosts.dup options[:platform_tag_confines] = [ :platform => /#{default[:platform]}/, :tag_reason_hash => { 'tag1' => 'reason1' } ] begin tag( 'tag1' ) rescue Beaker::DSL::Outcomes::SkipTest => e if e.message =~ /^No\ suitable\ hosts\ found/ # SkipTest is raised in the case when there are no hosts leftover for a test # after confining. It's a very common acceptance test case where all of the # hosts involved are of the same platform, and are thus all confined # away by the code being run here. In this case, the hosts object will not # be altered, but should be considered a pass, since the fact that SkipTest # is being raised confirms that a lower number of hosts are coming out of # the confine (0) than came in (>0, according to our pre-condition assertion) else fail "#{pstc_method_name} raised unexpected SkipTest exception: #{e}" end else assert hosts.length() < previous_hosts.length(), "#{pstc_method_name} did not change hosts array" end # cleanup options[:platform_tag_confines_object] = nil options[:platform_tag_confines] = nil @hosts = previous_hosts end endbeaker-4.30.0/acceptance/tests/base/dsl/structure_test.rb000066400000000000000000000065641407603575700234300ustar00rootroot00000000000000test_name "dsl::structure" do step "#confine_block runs specified block on matching hosts" do begin @in_confine = 0 confine_block :to, :platform => default["platform"] do @in_confine +=1 end assert_equal 1, @in_confine, "#confine_block did not run the supplied block" rescue Beaker::DSL::Outcomes::SkipTest => e fail "#confine_block raised unexpected SkipTest exception: #{e}" end end step "#confine_block leaves hosts array intact after running block on matching hosts" do begin previous_hosts = hosts.dup @in_confine = 0 confine_block :to, :platform => default["platform"] do @in_confine +=1 end assert_equal 1, @in_confine, "#confine_block did not run the supplied block" assert_equal hosts.dup, hosts, "#confine_block did not preserve the hosts array" rescue Beaker::DSL::Outcomes::SkipTest => e fail "#confine_block raised unexpected SkipTest exception: #{e}" end end step "#confine_block will not run specified block on non-matching hosts" do begin @in_confine = 0 confine_block :except, :platform => default["platform"] do @in_confine +=1 end assert_equal 0, @in_confine, "#confine_block did not skip the supplied block" rescue Beaker::DSL::Outcomes::SkipTest => e fail "#confine_block raised unexpected SkipTest exception: #{e}" end end step "#confine_block leaves hosts array intact after skipping block on non-matching hosts" do begin previous_hosts = hosts.dup @in_confine = 0 confine_block :except, :platform => default["platform"] do @in_confine +=1 end assert_equal 0, @in_confine, "#confine_block did not skip the supplied block" assert_equal hosts.dup, hosts, "#confine_block did not preserve the hosts array" rescue Beaker::DSL::Outcomes::SkipTest => e fail "#confine_block raised unexpected SkipTest exception: #{e}" end end step "#confine_block allows blocks to raise skip_test" do previous_hosts = hosts.dup begin @in_confine = 0 confine_block :to, :platform => default["platform"] do @in_confine +=1 skip_test "this block raises a skip" end rescue Beaker::DSL::Outcomes::SkipTest => e assert_match /this block raises a skip/, e.message, "#confine_block raised an unexpected skip_test" assert_equal 1, @in_confine, "#confine_block did not execute supplied block" assert_equal hosts.dup, hosts, "#confine_block did not preserve the hosts array" end end step "#confine reports correct message and skips test when criteria doesn't match on 'to'" do begin confine :to, { :platform => 'test' } fail "#confine did not skip test but should have." rescue Beaker::DSL::Outcomes::SkipTest => e assert_match /No suitable hosts found with {:platform=>"test"}/, e.message, "#confine raised an unexpected skip_test" end end step "#confine reports correct message and skips test when criteria doesn't match on 'except'" do begin confine :except, { :platform => default['platform'] } fail "#confine did not skip test but should have." rescue Beaker::DSL::Outcomes::SkipTest => e assert_match /No suitable hosts found without {:platform=>"#{default['platform']}"}/, e.message, "#confine raised an unexpected # skip_test" end end end beaker-4.30.0/acceptance/tests/base/external_resources_test.rb000066400000000000000000000021371407603575700245120ustar00rootroot00000000000000test_name 'External Resources Test' do step 'Verify EPEL resources are up and available' do def build_url(el_version) url_base = options[:epel_url] "#{url_base}/epel-release-latest-#{el_version}.noarch.rpm" end def epel_url_test(el_version) url = build_url(el_version) # -I option just asks for headers, not looking to download the package curl_cmd = Command.new("curl -I #{url}") host = default curl_headers_result = Result.new(host, curl_cmd) curl_fail_msg = "EPEL curl failed, waiting for fibonacci backoff to retry..." repeat_fibonacci_style_for(10) do curl_headers_result = host.exec(curl_cmd) curl_succeeded = curl_headers_result.exit_code == 0 logger.info(curl_fail_msg) unless curl_succeeded curl_succeeded end assert_match(/200 OK/, curl_headers_result.stdout, "EPEL #{el_version} should be reachable at #{url}") end step 'Verify el_version numbers 6,7,8 are found on the epel resource' do [6,7,8].each do |el_version| epel_url_test(el_version) end end end end beaker-4.30.0/acceptance/tests/base/host/000077500000000000000000000000001407603575700201645ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/host/file_test.rb000066400000000000000000000052541407603575700224750ustar00rootroot00000000000000test_name 'File Test' do # some shared setup stuff hosts.each do |host| host.user_present('testuser') host.group_present('testgroup') end step "#chown changes file user ownership" do hosts.each do |host| # create a tmp file to mangle tmpfile = host.tmpfile('beaker') # ensure we have a user to chown to host.chown('testuser', tmpfile) assert_match /testuser/, host.ls_ld(tmpfile), "Should have found testuser in `ls -ld` output" end end step "#chown changes directory user ownership" do hosts.each do |host| # create a tmp file to mangle tmpdir = host.tmpdir('beaker') # ensure we have a user to chown to host.chgrp('testgroup', tmpdir) assert_match /testgroup/, host.ls_ld(tmpdir), "Should have found testgroup in `ls -ld` output: #{stdout}" end end step "#chown changes directory user ownership recursively" do hosts.each do |host| # create a tmp file to mangle tmpdir = host.tmpdir('beaker') on host, host.touch("#{tmpdir}/somefile.txt", false) host.chown('testuser', tmpdir, true) assert_match /testuser/, host.ls_ld("#{tmpdir}/somefile.txt"), "Should have found testuser in `ls -ld` output for sub-file" end end step "#chgrp changes file group ownership" do hosts.each do |host| # create a tmp file to mangle tmpfile = host.tmpfile('beaker') # ensure we have a group to chgrp to host.chgrp('testgroup', tmpfile) assert_match /testgroup/, host.ls_ld(tmpfile), "Should have found testgroup in `ls -ld` output" end end step "#chgrp changes directory group ownership" do hosts.each do |host| # create a tmp file to mangle tmpdir = host.tmpdir('beaker') # ensure we have a group to chgrp to host.chgrp('testgroup', tmpdir) assert_match /testgroup/, host.ls_ld(tmpdir), "Should have found testgroup in `ls -ld` output" end end step "#chgrp changes directory group ownership recursively" do hosts.each do |host| # create a tmp file to mangle tmpdir = host.tmpdir('beaker') on host, host.touch("#{tmpdir}/somefile.txt", false) host.chgrp('testgroup', tmpdir, true) assert_match /testgroup/, host.ls_ld("#{tmpdir}/somefile.txt"), "Should have found testgroup in `ls -ld` output for sub-file" end end step "#ls_ld produces output" do hosts.each do |host| # create a tmp file to mangle tmpdir = host.tmpdir('beaker') assert_match /beaker/, host.ls_ld(tmpdir), "Should have found beaker in `ls -ld` output" end end # shared teardown hosts.each do |host| host.user_absent('testuser') host.group_absent('testgroup') end endbeaker-4.30.0/acceptance/tests/base/host/group_test.rb000066400000000000000000000011051407603575700227010ustar00rootroot00000000000000test_name 'Group Test' do step "#group_get: has an Administrators group on Windows" do hosts.select { |h| h['platform'] =~ /windows/ }.each do |host| host.group_get('Administrators') do |result| refute_match(result.stdout, '1376', 'Output indicates Administrators not found') end end end step "#group_get: should not have CroMags group on Windows" do hosts.select { |h| h['platform'] =~ /windows/ }.each do |host| assert_raises Beaker::Host::CommandFailure do host.group_get('CroMags') { |result| } end end end end beaker-4.30.0/acceptance/tests/base/host/host_test.rb000066400000000000000000000324671407603575700225410ustar00rootroot00000000000000require "helpers/test_helper" test_name "confirm host object behave correctly" step "#port_open? : can determine if a port is open on hosts" hosts.each do |host| logger.debug "port 22 (ssh) should be open on #{host}" assert_equal(true, host.port_open?(22), "port 22 on #{host} should be open") logger.debug "port 65535 should be closed on #{host}" assert_equal(false, host.port_open?(65535), "port 65535 on #{host} should be closed") end step "#ip : can determine the ip address on hosts" hosts.each do |host| ip = host.ip # confirm ip format logger.debug("format of #{ip} for #{host} should be correct") assert_match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/, ip, "#{ip} on #{host} isn't correct format") end step "#is_x86_64? : can determine arch on hosts" hosts.each do |host| if host['platform'] =~ /x86_64|_64|amd64|-64/ assert_equal(true, host.is_x86_64?, "is_x86_64? should be true on #{host}: #{host['platform']}") else assert_equal(false, host.is_x86_64?, "is_x86_64? should be false on #{host}: #{host['platform']}") end end step "#get_env_var : can get a specific environment variable" hosts.each do |host| env_prefix = 'BEAKER' + SecureRandom.hex(4).upcase env_param1 = "#{env_prefix}_p1" env_value1 = "#{env_prefix}_v1" host.clear_env_var(env_param1) host.add_env_var(env_param1,env_value1) val = host.get_env_var(env_param1) assert_match(/^#{env_param1}=#{env_value1}$/, val, "get_env_var can get a specific environment variable") end step "#get_env_var : should not match a partial env key name" hosts.each do |host| env_id = 'BEAKER' + SecureRandom.hex(4).upcase # Used as a prefix env_param1 = "#{env_id}_pre" env_value1 = "#{env_id}_pre" # Used as a suffix env_param2 = "suf_#{env_id}" env_value2 = "suf_#{env_id}" # Used as a infix env_param3 = "in_#{env_id}_in" env_value3 = "in_#{env_id}_in" host.clear_env_var(env_param1) host.clear_env_var(env_param2) host.clear_env_var(env_param3) host.add_env_var(env_param1,env_value1) host.add_env_var(env_param1,env_value2) host.add_env_var(env_param1,env_value3) val = host.get_env_var(env_id) assert('' == val,'get_env_var should not match a partial env key name') end step "#get_env_var : should not return a match from a key\'s value" hosts.each do |host| env_prefix = 'BEAKER' + SecureRandom.hex(4).upcase env_param1 = "#{env_prefix}_p1" env_value1 = "#{env_prefix}_v1" host.clear_env_var(env_param1) host.add_env_var(env_param1,env_value1) val = host.get_env_var(env_value1) assert('' == val,'get_env_var should not return a match from a key\'s value') end step "#clear_env_var : should only remove the specified key" hosts.each do |host| # Note - Must depend on `SecureRandom.hex(4)` creating a unique key as unable to depend on the function under test `clear_env_var` env_id = 'BEAKER' + SecureRandom.hex(4).upcase # Use env_id as a suffix env_param1 = "p1_#{env_id}" env_value1 = "v1_#{env_id}" # Use env_id as a prefix env_param2 = "#{env_id}_p2" env_value2 = "#{env_id}_v2" # Use env_id a key to delete env_param3 = "#{env_id}" env_value3 = "#{env_id}" host.add_env_var(env_param1,env_value1) host.add_env_var(env_param2,env_value2) host.add_env_var(env_param3,env_value3) host.clear_env_var(env_param3) val = host.get_env_var(env_param1) assert_match(/^#{env_param1}=#{env_value1}$/, val, "#{env_param1} should exist after calling clear_env_var") val = host.get_env_var(env_param2) assert_match(/^#{env_param2}=#{env_value2}$/, val, "#{env_param2} should exist after calling clear_env_var") val = host.get_env_var(env_param3) assert('' == val,"#{env_param3} should not exist after calling clear_env_var") end step "#add_env_var : can add a unique environment variable" hosts.each do |host| env_id = 'BEAKER' + SecureRandom.hex(4).upcase env_param1 = "#{env_id}" env_value1 = "#{env_id}" # Use env_id as a prefix env_param2 = "#{env_id}_pre" env_value2 = "#{env_id}_pre" # Use env_id as a suffix env_param3 = "suf_#{env_id}" env_value3 = "suf_#{env_id}" host.clear_env_var(env_param1) host.clear_env_var(env_param2) host.clear_env_var(env_param3) host.add_env_var(env_param1,env_value1) host.add_env_var(env_param2,env_value2) host.add_env_var(env_param3,env_value3) val = host.get_env_var(env_param1) assert_match(/^#{env_param1}=#{env_value1}$/, val, "#{env_param1} should exist") val = host.get_env_var(env_param2) assert_match(/^#{env_param2}=#{env_value2}$/, val, "#{env_param2} should exist") val = host.get_env_var(env_param3) assert_match(/^#{env_param3}=#{env_value3}$/, val, "#{env_param3} should exist") end step "#add_env_var : can add an environment variable" hosts.each do |host| host.clear_env_var("test") logger.debug("add TEST=1") host.add_env_var("TEST", "1") logger.debug("add TEST=1 again (shouldn't create duplicate entry)") host.add_env_var("TEST", "1") logger.debug("add TEST=2") host.add_env_var("TEST", "2") logger.debug("ensure that TEST env var has correct setting") logger.debug("add TEST=3") host.add_env_var("TEST", "3") logger.debug("ensure that TEST env var has correct setting") val = host.get_env_var("TEST") assert_match(/TEST=3(;|:)2(;|:)1$/, val, "add_env_var can correctly add env vars") end step "#add_env_var : can preserve an environment between ssh connections" hosts.each do |host| host.clear_env_var("TEST") logger.debug("add TEST=1") host.add_env_var("TEST", "1") logger.debug("add TEST=1 again (shouldn't create duplicate entry)") host.add_env_var("TEST", "1") logger.debug("add TEST=2") host.add_env_var("TEST", "2") logger.debug("ensure that TEST env var has correct setting") logger.debug("add TEST=3") host.add_env_var("TEST", "3") logger.debug("close the connection") host.close logger.debug("ensure that TEST env var has correct setting") val = host.get_env_var("TEST") assert_match(/TEST=3(;|:)2(;|:)1$/, val, "can preserve an environment between ssh connections") end step "#delete_env_var : can delete an environment" hosts.each do |host| logger.debug("remove TEST=3") host.delete_env_var("TEST", "3") val = host.get_env_var("TEST") assert_match(/TEST=2(;|:)1$/, val, "delete_env_var can correctly delete part of a chained env var") logger.debug("remove TEST=1") host.delete_env_var("TEST", "1") val = host.get_env_var("TEST") assert_match(/TEST=2$/, val, "delete_env_var can correctly delete part of a chained env var") logger.debug("remove TEST=2") host.delete_env_var("TEST", "2") val = host.get_env_var("TEST") assert_equal("", val, "delete_env_var fully removes empty env var") end step "#mkdir_p : can recursively create a directory structure on a host" hosts.each do |host| #clean up first! host.rm_rf("test1") #test dir construction logger.debug("create test1/test2/test3/test4") assert_equal(true, host.mkdir_p("test1/test2/test3/test4"), "can create directory structure") logger.debug("should be able to create a file in the new dir") on host, host.touch("test1/test2/test3/test4/test.txt", false) end step "#do_scp_to : can copy a directory to the host with no ignores" current_dir = File.dirname(__FILE__) module_fixture = File.join(current_dir, "../../../fixtures/module") hosts.each do |host| logger.debug("can recursively copy a module over") #make sure that we are clean on the test host host.rm_rf("module") host.do_scp_to(module_fixture, ".", {}) Dir.mktmpdir do |tmp_dir| #grab copy from host host.do_scp_from("module", tmp_dir, {}) #compare to local copy local_paths = Dir.glob(File.join(module_fixture, "**/*")).select { |f| File.file?(f) } host_paths = Dir.glob(File.join(File.join(tmp_dir, "module"), "**/*")).select { |f| File.file?(f) } #each local file should have a single match on the host local_paths.each do |path| search_name = path.gsub(/^.*fixtures\//, '') #reduce down to the path that should match matched = host_paths.select{ |check| check =~ /#{Regexp.escape(search_name)}$/ } assert_equal(1, matched.length, "should have found a single instance of path #{search_name}, found #{matched.length}: \n #{matched}") host_paths = host_paths - matched end assert_equal(0, host_paths.length, "there are extra paths on #{host} (#{host_paths})") end end step "#do_scp_to with :ignore : can copy a dir to the host, excluding ignored patterns that DO NOT appear in the source absolute path" current_dir = File.dirname(__FILE__) module_fixture = File.expand_path(File.join(current_dir, "../../../fixtures/module")) hosts.each do |host| logger.debug("can recursively copy a module over, ignoring some files/dirs") #make sure that we are clean on the test host host.rm_rf("module") host.do_scp_to(module_fixture, ".", {:ignore => ['tests', 'Gemfile']}) Dir.mktmpdir do |tmp_dir| #grab copy from host host.do_scp_from("module", tmp_dir, {}) #compare to local copy local_paths = Dir.glob(File.join(module_fixture, "**/*")).select { |f| File.file?(f) } host_paths = Dir.glob(File.join(File.join(tmp_dir, "module"), "**/*")).select { |f| File.file?(f) } #each local file should have a single match on the host local_paths.each do |path| search_name = path.gsub(/^.*fixtures\//, '') #reduce down to the path that should match matched = host_paths.select{ |check| check =~ /#{Regexp.escape(search_name)}$/ } re = /((\/|\A)tests(\/|\z))|((\/|\A)Gemfile(\/|\z))/ if path !~ re assert_equal(1, matched.length, "should have found a single instance of path #{search_name}, found #{matched.length}: \n #{matched}") else assert_equal(0, matched.length, "should have found no instances of path #{search_name}, found #{matched.length}: \n #{matched}") end host_paths = host_paths - matched end assert_equal(0, host_paths.length, "there are extra paths on #{host} (#{host_paths})") end end step "#do_scp_to with :ignore : can copy a dir to the host, excluding ignored patterns that DO appear in the source absolute path" current_dir = File.dirname(__FILE__) module_fixture = File.expand_path(File.join(current_dir, "../../../fixtures/module")) hosts.each do |host| logger.debug("can recursively copy a module over, ignoring some sub-files/sub-dirs that also appear in the absolute path") #make sure that we are clean on the test host host.rm_rf("module") host.do_scp_to(module_fixture, ".", {:ignore => ['module', 'Gemfile']}) Dir.mktmpdir do |tmp_dir| #grab copy from host host.do_scp_from("module", tmp_dir, {}) #compare to local copy local_paths = Dir.glob(File.join(module_fixture, "**/*")).select { |f| File.file?(f) } host_paths = Dir.glob(File.join(File.join(tmp_dir, "module"), "**/*")).select { |f| File.file?(f) } #each local file should have a single match on the host local_paths.each do |path| search_name = path.gsub(/^.*fixtures\/module\//, '') #reduce down to the path that should match matched = host_paths.select{ |check| check =~ /#{Regexp.escape(search_name)}$/ } re = /((\/|\A)module(\/|\z))|((\/|\A)Gemfile(\/|\z))/ if path.gsub(/^.*module\//, '') !~ re assert_equal(1, matched.length, "should have found a single instance of path #{search_name}, found #{matched.length}: \n #{matched}") else assert_equal(0, matched.length, "should have found no instances of path #{search_name}, found #{matched.length}: \n #{matched}") end host_paths = host_paths - matched end assert_equal(0, host_paths.length, "there are extra paths on #{host} (#{host_paths})") end end step "Ensure scp errors close the ssh connection" do step 'Attempt to generate a remote file that does not exist' do # This assert relies on the behavior of the net-scp library to # raise an error when #channel.on_close is called, which is called by # indirectly called by beaker's own SshConnection #close mehod. View # the source for further info: # https://github.com/net-ssh/net-sacp/blob/master/lib/net/scp.rb assert_raises Net::SCP::Error do create_remote_file(default, '/tmp/this/path/cannot/possibly/exist.txt', "contents") end end step 'Ensure that a subsequent ssh connection works' do # If the ssh connection was left in a dangling state, then this #on call will hang on default, 'true' end step 'Attempt to scp from a resource on the SUT that does not exist' do # This assert relies on the behavior of the net-scp library to # use the Dir.mkdir method in the #download_start_state method. # See the source for further info: # https://github.com/net-ssh/net-sacp/blob/master/lib/net/scp/download.rb assert_raises Errno::ENOENT do scp_from default, '/tmp/path/dne/wtf/bbq', '/tmp/path/dne/wtf/bbq' end end step 'Ensure that a subsequent ssh connection works' do # If the ssh connection was left in a dangling state, then this #on call will hang on default, 'true' end end step 'Ensure that a long 128+ character string with UTF-8 characters does not break net-ssh' do long_string = 'a' * 128 + "\u06FF" on(default, "mkdir /tmp/#{long_string}") result = on(default, 'ls /tmp') assert(result.stdout.include?(long_string), 'Error in folder creation with long string + UTF-8 characters') # remove the folder on(default, "rm -rf /tmp/#{long_string}") result = on(default, 'ls /tmp') assert(!result.stdout.include?(long_string), 'Error in folder deletion with long string + UTF-8 characters') end beaker-4.30.0/acceptance/tests/base/host/packages.rb000066400000000000000000000067401407603575700222760ustar00rootroot00000000000000test_name 'confirm packages on hosts behave correctly' confine :except, :platform => %w(osx) def get_host_pkg(host) case when host['platform'] =~ /sles-10/ Beaker::HostPrebuiltSteps::SLES10_PACKAGES when host['platform'] =~ /opensuse|sles-/ Beaker::HostPrebuiltSteps::SLES_PACKAGES when host['platform'] =~ /debian/ Beaker::HostPrebuiltSteps::DEBIAN_PACKAGES when host['platform'] =~ /cumulus/ Beaker::HostPrebuiltSteps::CUMULUS_PACKAGES when (host['platform'] =~ /windows/ and host.is_cygwin?) Beaker::HostPrebuiltSteps::WINDOWS_PACKAGES when (host['platform'] =~ /windows/ and not host.is_cygwin?) Beaker::HostPrebuiltSteps::PSWINDOWS_PACKAGES when host['platform'] =~ /freebsd/ Beaker::HostPrebuiltSteps::FREEBSD_PACKAGES when host['platform'] =~ /openbsd/ Beaker::HostPrebuiltSteps::OPENBSD_PACKAGES when host['platform'] =~ /solaris-10/ Beaker::HostPrebuiltSteps::SOLARIS10_PACKAGES else Beaker::HostPrebuiltSteps::UNIX_PACKAGES end end step '#check_for_command : can determine where a command exists' hosts.each do |host| logger.debug "echo package should be installed on #{host}" assert(host.check_for_command('echo'), "'echo' should be a command") logger.debug("doesnotexist package should not be installed on #{host}") assert_equal(false, host.check_for_command('doesnotexist'), '"doesnotexist" should not be a command') end step '#check_for_package : can determine if a package is installed' hosts.each do |host| package = get_host_pkg(host)[0] logger.debug "#{package} package should be installed on #{host}" assert(host.check_for_package(package), "'#{package}' should be installed") logger.debug("doesnotexist package should not be installed on #{host}") assert_equal(false, host.check_for_package('doesnotexist'), '"doesnotexist" should not be installed') end step '#install_package and #uninstall_package : remove and install a package successfully' hosts.each do |host| # this works on Windows as well, althought it pulls in # a lot of dependencies. # skipping this test for windows since it requires a restart next if host['platform'] =~ /windows/ package = 'zsh' package = 'CSWzsh' if host['platform'] =~ /solaris-10/ package = 'git' if host['platform'] =~ /opensuse|sles/ if host['platform'] =~ /solaris-11/ logger.debug("#{package} should be uninstalled on #{host}") host.uninstall_package(package) assert_equal(false, host.check_for_package(package), "'#{package}' should not be installed") end assert_equal(false, host.check_for_package(package), "'#{package}' not should be installed") logger.debug("#{package} should be installed on #{host}") cmdline_args = '' # Newer vmpooler hosts created by Packer templates, and running Cygwin 2.4, # must have these switches passed cmdline_args = '--local-install --download' if (host['platform'] =~ /windows/ and host.is_cygwin?) host.install_package(package, cmdline_args) assert(host.check_for_package(package), "'#{package}' should be installed") # windows does not support uninstall_package unless host['platform'] =~ /windows/ logger.debug("#{package} should be uninstalled on #{host}") host.uninstall_package(package) if host['platform'] =~ /debian/ assert_equal(false, host.check_for_command(package), "'#{package}' should not be installed or available") else assert_equal(false, host.check_for_package(package), "'#{package}' should not be installed") end end end beaker-4.30.0/acceptance/tests/base/host/packages_unix.rb000066400000000000000000000032701407603575700233340ustar00rootroot00000000000000test_name 'confirm unix-specific package methods work' confine :except, :platform => %w(windows solaris osx) current_dir = File.dirname(__FILE__) pkg_fixtures = File.expand_path(File.join(current_dir, '../../../fixtures/package')) pkg_name = 'puppetserver' def clean_file(host, file) unless file.nil? filename = pkg_file(host, file) if !filename.nil? && host.file_exist?(filename) on(host, "rm -rf #{filename}") end end end def pkg_file(host, pkg_name) if host['platform'] =~ /debian|ubuntu/ "/etc/apt/sources.list.d/#{pkg_name}.list" elsif host['platform'] =~ /el/ "/etc/yum.repos.d/#{pkg_name}.repo" else nil end end step '#update_apt_if_needed : can execute without raising an error' hosts.each do |host| host.update_apt_if_needed end step '#deploy_apt_repo : deploy puppet-server nightly repo' hosts.each do |host| if host['platform'] =~ /debian|ubuntu/ clean_file(host, pkg_name) host.deploy_apt_repo(pkg_fixtures, pkg_name, 'latest') assert(host.file_exist?(pkg_file(host, pkg_name)), 'apt file should exist') clean_file(host, pkg_name) end end step '#deploy_yum_repo : deploy puppet-server nightly repo' hosts.each do |host| if host['platform'] =~ /el/ clean_file(host, pkg_name) host.deploy_yum_repo(pkg_fixtures, pkg_name, 'latest') assert(host.file_exist?(pkg_file(host, pkg_name)), 'yum file should exist') clean_file(host, pkg_name) end end step '#deploy_package_repo : deploy puppet-server nightly repo' hosts.each do |host| next if host['platform'].variant == 'sles' && Integer(host['platform'].version) < 12 host.deploy_package_repo(pkg_fixtures, pkg_name, 'latest') clean_file(host, pkg_name) end beaker-4.30.0/acceptance/tests/base/host/user_test.rb000066400000000000000000000011201407603575700225200ustar00rootroot00000000000000test_name 'User Test' do step "#user_get: has an Administrator user on Windows" do hosts.select { |h| h['platform'] =~ /windows/ }.each do |host| host.user_get('Administrator') do |result| refute_match(result.stdout, 'NET HELPMSG', 'Output indicates Administrator not found') end end end step "#user_get: should not have CaptainCaveman user on Windows" do hosts.select { |h| h['platform'] =~ /windows/ }.each do |host| assert_raises Beaker::Host::CommandFailure do host.user_get('CaptainCaveman') { |result| } end end end end beaker-4.30.0/acceptance/tests/base/host_prebuilt_steps/000077500000000000000000000000001407603575700233105ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/host_prebuilt_steps/ssh_environment_test.rb000066400000000000000000000017221407603575700301170ustar00rootroot00000000000000test_name "confirm host prebuilt steps behave correctly" do confine_block :except, :platform => /f5|windows/ do step "confirm ssh environment file existence" do hosts.each do |host| assert(host.file_exist?(host[:ssh_env_file])) end end step "confirm PATH env variable is set in the ssh environment file" do hosts.each do |host| assert(0 == on(host, "grep \"PATH\" #{host[:ssh_env_file]}").exit_code) end end end confine_block :to, :platform => /solaris-10/ do step "confirm /opt/csw/bin has been added to the path" do hosts.each do |host| assert(0 == on(host, "grep \"/opt/csw/bin\" #{host[:ssh_env_file]}").exit_code) end end end confine_block :to, :platform => /openbsd/ do step "confirm PKG_PATH is set in the ssh environment file" do hosts.each do |host| assert(0 == on(host, "grep \"PKG_PATH\" #{host[:ssh_env_file]}").exit_code) end end end endbeaker-4.30.0/acceptance/tests/base/test_suite/000077500000000000000000000000001407603575700213775ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/base/test_suite/export.rb000066400000000000000000000004761407603575700232540ustar00rootroot00000000000000test_name 'ensure tests can export arbitrary data' do step 'export nested hash' do export({'middle earth' => { 'Hobbits' => ['Bilbo', 'Frodo'], 'Elves' => 'Arwen', :total => {'numbers' => 42} } }) export({'another' => 'author'}) end end beaker-4.30.0/acceptance/tests/hypervisor/000077500000000000000000000000001407603575700205075ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/hypervisor/communication_test.rb000066400000000000000000000002601407603575700247360ustar00rootroot00000000000000# hosts should be able to talk to each other by name step "hosts can ping each other" hosts.each do |one| hosts.each do |two| assert_equal(one.ping(two), true) end end beaker-4.30.0/acceptance/tests/install/000077500000000000000000000000001407603575700177435ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/install/from_file.rb000066400000000000000000000011661407603575700222360ustar00rootroot00000000000000test_name 'test generic installers' confine :except, :platform => /^windows|osx/ step 'install arbitrary msi via url' do hosts.each do |host| if host['platform'] =~ /win/ # this should be implemented at the host/win/pkg.rb level someday generic_install_msi_on(host, 'https://releases.hashicorp.com/vagrant/1.8.4/vagrant_1.8.4.msi', {}, {:debug => true}) end end end step 'install arbitrary dmg via url' do hosts.each do |host| if host['platform'] =~ /osx/ host.generic_install_dmg('https://releases.hashicorp.com/vagrant/1.8.4/vagrant_1.8.4.dmg', 'Vagrant', 'Vagrant.pkg') end end end beaker-4.30.0/acceptance/tests/load_path_bootstrap.rb000066400000000000000000000006331407603575700226540ustar00rootroot00000000000000# Ensure that `$LOAD_PATH` is set up properly, in cases where the entire # acceptance suite is being run, but the options file # `acceptance/config/acceptance_options.rb` was not specified via the # `--options-file` command-line argument. begin require 'helpers/test_helper' rescue LoadError $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) require 'helpers/test_helper' end beaker-4.30.0/acceptance/tests/subcommands/000077500000000000000000000000001407603575700206105ustar00rootroot00000000000000beaker-4.30.0/acceptance/tests/subcommands/destroy.rb000066400000000000000000000043541407603575700226340ustar00rootroot00000000000000test_name 'use the destroy subcommand' do def delete_root_folder_contents on default, 'rm -rf /root/* /root/.beaker' end step 'ensure that `beaker destroy` fails correctly when a configuration has not been initialized' do delete_root_folder_contents result = on(default, 'beaker destroy', :accept_all_exit_codes => true) assert_match(/Please provision an environment/, result.stdout) assert_equal(1, result.exit_code, '`beaker destroy` in an uninitialised configuration should return a non-zero exit code') end step 'ensure that `beaker help destroy` works' do result = on(default, 'beaker help destroy') assert_match(/Usage/, result.stdout) assert_equal(0, result.exit_code, '`beaker help destroy` should return a zero exit code') end step 'ensure that `beaker destroy --help` works' do result = on(default, 'beaker destroy --help') assert_match(/Usage/, result.stdout) assert_equal(0, result.exit_code, '`beaker destroy --help` should return a zero exit code') end step 'ensure that `beaker destroy` destroys vmpooler configuration' do delete_root_folder_contents result = on(default, "beaker init --hosts centos6-64") assert_match(/Writing configured options to disk/, result.stdout) assert_equal(0, result.exit_code, "`beaker init` should return a zero exit code") step 'ensure destroy fails to run against an unprovisioned environment' do result = on(default, "beaker destroy", :accept_all_exit_codes => true) assert_match(/Please provision an environment/, result.stdout) assert_equal(1, result.exit_code, "`beaker destroy` should return a non zero exit code") end step 'ensure provision provisions, validates, and configures new hosts' do result = on(default, "beaker provision") assert_match(/Using available host/, result.stdout) assert_equal(0, result.exit_code, "`beaker provision` should return a zero exit code") end step 'ensure destroy will destroy a provisioned environment' do result = on(default, 'beaker destroy') assert_match(/Handing/, result.stdout) assert_equal(0, result.exit_code, "`beaker destroy` should return a zero exit code") end delete_root_folder_contents end end beaker-4.30.0/acceptance/tests/subcommands/exec.rb000066400000000000000000000020041407603575700220550ustar00rootroot00000000000000test_name 'use the exec subcommand' do def delete_root_folder_contents on default, 'rm -rf /root/* /root/.beaker' end step 'ensure the workspace is clean' do delete_root_folder_contents end step 'run init and provision to set up the system' do on default, 'beaker init --hosts centos6-64; beaker provision' subcommand_state = on(default, 'cat .beaker/.subcommand_state.yaml').stdout subcommand_state = YAML.parse(subcommand_state).to_ruby assert_equal(true, subcommand_state['provisioned']) end step 'create a test dir and populate it with tests' do on default, 'mkdir -p testing_dir' end step 'create remote test file' do testfile = <<-TESTFILE on(agents, 'echo hello world') TESTFILE create_remote_file(default, '/root/testing_dir/testfile1.rb', testfile) end step 'specify that remote file with beaker exec' do result = on(default, 'beaker exec testing_dir/testfile1.rb --log-level verbose') assert_match(/hello world/, result.stdout) end end beaker-4.30.0/acceptance/tests/subcommands/init.rb000066400000000000000000000023701407603575700221020ustar00rootroot00000000000000test_name 'use the init subcommand' do SubcommandUtil = Beaker::Subcommands::SubcommandUtil def delete_root_folder_contents on default, 'rm -rf /root/* /root/.beaker' end step 'ensure beaker init requires hosts flag' do result = on(default, 'beaker init') assert_match(/No value(.+)--hosts/, result.raw_output) end step 'ensure beaker init writes YAML configuration files to disk' do delete_root_folder_contents on(default, 'beaker init --hosts centos6-64') subcommand_options = on(default, "cat #{SubcommandUtil::SUBCOMMAND_OPTIONS}").stdout subcommand_state = on(default, "cat #{SubcommandUtil::SUBCOMMAND_STATE}").stdout parsed_options = YAML.parse(subcommand_options).to_ruby assert(parsed_options["HOSTS"].count == 1) assert(parsed_options.class == Hash) assert(YAML.parse(subcommand_state).to_ruby.class == Hash) end step 'ensure beaker init saves beaker-run arguments to the subcommand_options.yaml' do delete_root_folder_contents on(default, 'beaker init --log-level verbose --hosts centos6-64') subcommand_options = on(default, "cat #{SubcommandUtil::SUBCOMMAND_OPTIONS}").stdout hash = YAML.parse(subcommand_options).to_ruby assert_equal('verbose', hash['log_level']) end end beaker-4.30.0/acceptance/tests/subcommands/provision.rb000066400000000000000000000013511407603575700231650ustar00rootroot00000000000000test_name 'use the provision subcommand' do SubcommandUtil = Beaker::Subcommands::SubcommandUtil def delete_root_folder_contents on default, 'rm -rf /root/* /root/.beaker' end step 'run beaker init and provision' do delete_root_folder_contents result = on(default, 'beaker provision --hosts centos6-64') assert_match(/ERROR(.+)--hosts/, result.raw_output) on(default, 'beaker init --hosts centos6-64') result = on(default, 'beaker provision') assert_match(/Using available host/, result.stdout) subcommand_state = on(default, "cat #{SubcommandUtil::SUBCOMMAND_STATE}").stdout subcommand_state = YAML.parse(subcommand_state).to_ruby assert_equal(true, subcommand_state['provisioned']) end end beaker-4.30.0/beaker.gemspec000066400000000000000000000043451407603575700156510ustar00rootroot00000000000000# -*- encoding: utf-8 -*- lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'beaker/version' Gem::Specification.new do |s| s.name = "beaker" s.version = Beaker::Version::STRING s.authors = ["Puppet"] s.email = ["voxpupuli@groups.io"] s.homepage = "https://github.com/voxpupuli/beaker" s.summary = %q{Let's test Puppet!} s.description = %q{Puppet's accceptance testing harness} s.license = 'Apache-2.0' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.required_ruby_version = Gem::Requirement.new('>= 2.4') # Testing dependencies s.add_development_dependency 'rspec', '~> 3.0' s.add_development_dependency 'rspec-its' s.add_development_dependency 'fakefs', '~> 1.0' s.add_development_dependency 'rake', '~> 13.0' # Provisioner dependencies - needed for acceptance tests # TODO: figure out how to remove these s.add_development_dependency 'beaker-aws', '~> 0.1' s.add_development_dependency 'beaker-abs', '>= 0.4' s.add_development_dependency 'beaker-vmpooler', '~> 1.0' # Documentation dependencies s.add_development_dependency 'yard', '~> 0.9.11' # Run time dependencies s.add_runtime_dependency 'minitest', '~> 5.4' s.add_runtime_dependency 'minitar', '~> 0.6' s.add_runtime_dependency 'pry-byebug', '~> 3.9' # pry-byebug can have issues with native readline libs so add rb-readline s.add_runtime_dependency 'rb-readline', '~> 0.5.3' s.add_runtime_dependency 'rexml' s.add_runtime_dependency 'hocon', '~> 1.0' s.add_runtime_dependency 'net-ssh', '>= 5.0' s.add_runtime_dependency 'net-scp', '>= 1.2', '< 4.0' s.add_runtime_dependency 'inifile', '~> 3.0' s.add_runtime_dependency 'rsync', '~> 1.0.9' s.add_runtime_dependency 'open_uri_redirections', '~> 0.2.1' s.add_runtime_dependency 'in-parallel', '~> 0.1' s.add_runtime_dependency 'thor', ['>= 1.0.1', '< 2.0'] # Run time dependencies that are Beaker libraries s.add_runtime_dependency 'stringify-hash', '~> 0.0' s.add_runtime_dependency 'beaker-hostgenerator' end beaker-4.30.0/bin/000077500000000000000000000000001407603575700136155ustar00rootroot00000000000000beaker-4.30.0/bin/beaker000077500000000000000000000004501407603575700147730ustar00rootroot00000000000000#!/usr/bin/env ruby require 'rubygems' unless defined?(Gem) require 'beaker' if Beaker::Subcommands::SubcommandUtil.execute_subcommand?(ARGV[0]) Beaker::Subcommand.start(ARGV) else Beaker::CLI.new.parse_options.provision.execute! puts "Beaker completed successfully, thanks." end exit 0 beaker-4.30.0/docs/000077500000000000000000000000001407603575700137755ustar00rootroot00000000000000beaker-4.30.0/docs/README.md000066400000000000000000000032401407603575700152530ustar00rootroot00000000000000![Beaker Muppet Image](http://images4.wikia.nocookie.net/__cb20101015151248/muppet/images/0/05/Beaker.jpg) Documentation for Beaker can be found in this repository in [the docs/ folder](). ## Table of Contents - [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. - [Concepts](concepts) discuss key topics and concepts at a fairly high level and provide useful background information and explanation. - [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. - [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. ## Other Resources 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: * [Latest Gem Release Notes](https://github.com/puppetlabs/beaker/blob/master/HISTORY.md#LATEST) * [Video: Beaker 101 talk at PDXPUG, May 2014](https://www.youtube.com/watch?v=cSyJXTYFXFg) * [Podcast: Beaker, May 2014](http://puppetlabs.com/podcasts/podcast-beaker-cloud-enabled-acceptance-testing-tool) * [Podcast: Automated Testing with Beaker for Windows, December 2014](http://puppetlabs.com/podcasts/podcast-automated-testing-beaker-windows) * [Real-world Examples: Puppetlabs Acceptance Testing for Puppet](https://github.com/puppetlabs/puppet/tree/master/acceptance/tests) beaker-4.30.0/docs/concepts/000077500000000000000000000000001407603575700156135ustar00rootroot00000000000000beaker-4.30.0/docs/concepts/argument_processing_and_precedence.md000066400000000000000000000360121407603575700252140ustar00rootroot00000000000000Beaker uses arguments and settings from a variety of sources to determine how your test run is executed. * [Environment Variables](#environment-variables) * [Host/Config File Options](#host-file-options) * [ARGV](#argv-or-provided-arguments-array) * [Supported Command Line Arguments](#supported-command-line-arguments) * [Options File Values](#options-file-values) * [Example Options File](#example-options-file) * [Default Values](#default-values) * [Beaker Default Values](#beaker-default-values) * [Priority of Settings](#priority-of-settings) ## Environment Variables ### Supported Environment Variables: ``` BEAKER VARIABLE NAME => ENVIRONMENT VARIABLE NAME :home => 'HOME', :project => ['BEAKER_PROJECT', 'BEAKER_project', 'JOB_NAME'], :department => ['BEAKER_DEPARTMENT', 'BEAKER_department'], :jenkins_build_url => ['BEAKER_BUILD_URL', 'BUILD_URL'], :created_by => ['BEAKER_CREATED_BY'], :consoleport => ['BEAKER_CONSOLEPORT', 'consoleport'], :is_pe => ['BEAKER_IS_PE', 'IS_PE'], :pe_dir => ['BEAKER_PE_DIR', 'pe_dist_dir'], :puppet_agent_version => ['BEAKER_PUPPET_AGENT_VERSION'], :puppet_agent_sha => ['BEAKER_PUPPET_AGENT_SHA'], :puppet_collection => ['BEAKER_PUPPET_COLLECTION'], :pe_version_file => ['BEAKER_PE_VERSION_FILE', 'pe_version_file'], :pe_ver => ['BEAKER_PE_VER', 'pe_ver'], :forge_host => ['BEAKER_FORGE_HOST', 'forge_host'], :package_proxy => ['BEAKER_PACKAGE_PROXY'], :release_apt_repo_url => ['BEAKER_RELEASE_APT_REPO', 'RELEASE_APT_REPO'], :release_yum_repo_url => ['BEAKER_RELEASE_YUM_REPO', 'RELEASE_YUM_REPO'], :dev_builds_url => ['BEAKER_DEV_BUILDS_URL', 'DEV_BUILDS_URL'], :vbguest_plugin => ['BEAKER_VB_GUEST_PLUGIN', 'BEAKER_vb_guest_plugin'], :tag_includes => ['BEAKER_TAG'], :tag_excludes => ['BEAKER_EXCLUDE_TAG'], :run_in_parallel => ['BEAKER_RUN_IN_PARALLEL'], ``` ## Host File Options Any values included for an individual host in a host file. ``` HOSTS: pe-ubuntu-lucid: roles: - agent - dashboard - database - master vmname : pe-ubuntu-lucid platform: ubuntu-10.04-i386 snapshot : clean-w-keys hypervisor : fusion ``` `roles`, `vmname`, `platform`, `snapshot` and `hypervisor` are all options set for the host `pe-ubuntu-lucid`. Any additional values can be included on a per-host basis by adding arbitrary key-value pairs. ## `CONFIG` section of Hosts File ``` HOSTS: pe-ubuntu-lucid: roles: - agent - dashboard - database - master vmname : pe-ubuntu-lucid platform: ubuntu-10.04-i386 snapshot : clean-w-keys hypervisor : fusion CONFIG: nfs_server: none consoleport: 443 pe_dir: http://path/to/pe/builds ``` `nfs_server`, `consoleport`, `pe_dir` are examples of `CONFIG` section arguments. The values of these will be rolled up into each host defined, thus `host[pe_dir]` is valid. ## ARGV or Provided Arguments Array ``` $ beaker --debug --tests acceptance/tests/base/host.rb --hosts configs/fusion/winfusion.cfg ``` `--debug`, `--tests acceptance/tests/base/host.rb` and `--hosts configs/fusion/winfusion.cfg` are the provided command line values for this test run. ### Supported Command Line Arguments: ``` $ beaker --help Usage: beaker [options...] -h, --hosts FILE Use host configuration FILE (default sample.cfg) -o, --options-file FILE Read options from FILE This should evaluate to a ruby hash. CLI optons are given precedence. --type TYPE one of git, foss, or pe used to determine underlying path structure of puppet install (default pe) --helper PATH/TO/SCRIPT Ruby file evaluated prior to tests (a la spec_helper) --load-path /PATH/TO/DIR,/ADDITIONAL/DIR/PATHS Add paths to LOAD_PATH -t /PATH/TO/DIR,/ADDITIONA/DIR/PATHS,/PATH/TO/FILE.rb, --tests Execute tests from paths and files --pre-suite /PRE-SUITE/DIR/PATH,/ADDITIONAL/DIR/PATHS,/PATH/TO/FILE.rb Path to project specific steps to be run BEFORE testing --post-suite /POST-SUITE/DIR/PATH,/OPTIONAL/ADDITONAL/DIR/PATHS,/PATH/TO/FILE.rb Path to project specific steps to be run AFTER testing --[no-]provision Do not provision vm images before testing (default: true) --[no-]configure Do not configure vm images before testing (default: true) --preserve-hosts [MODE] How should SUTs be treated post test Possible values: always (keep SUTs alive) onfail (keep SUTs alive if failures occur during testing) onpass (keep SUTs alive if no failures occur during testing) never (cleanup SUTs - shutdown and destroy any changes made during testing) (default: never) --root-keys Install puppetlabs pubkeys for superuser (default: false) --keyfile /PATH/TO/SSH/KEY Specify alternate SSH key (default: ~/.ssh/id_rsa) --timeout TIMEOUT (vCloud only) Specify a provisioning timeout (in seconds) (default: 300) -i, --install URI Install a project repo/app on the SUTs Provide full git URI or use short form KEYWORD/name supported keywords: PUPPET, FACTER, HIERA, HIERA-PUPPET -m, --modules URI Select puppet module git install URI -q, --[no-]quiet Do not log output to STDOUT (default: false) --[no-]color Do not display color in log output (default: true) --log-level LEVEL Log level Supported LEVEL keywords: trace : all messages, full stack trace of errors, file copy details debug : all messages, plus full stack trace of errors verbose : all messages info : info messages, notifications and warnings notify : notifications and warnings warn : warnings only (default: info) -d, --[no-]dry-run Report what would happen on targets (default: false) --fail-mode [MODE] How should the harness react to errors/failures Possible values: fast (skip all subsequent tests) slow (attempt to continue run post test failure) stop (DEPRECATED, please use fast) (default: slow) --[no-]ntp Sync time on SUTs before testing (default: false) --repo-proxy Proxy packaging repositories on ubuntu, debian, cumulus and solaris-11 (default: false) --add-el-extras Add Extra Packages for Enterprise Linux (EPEL) repository to el-* hosts (default: false) --package-proxy URL Set proxy url for package managers (yum and apt) --[no-]validate Validate that SUTs are correctly provisioned before running tests (default: true) --version Report currently running version of beaker --parse-only Display beaker parsed options and exit --help Display this screen -c, --config FILE DEPRECATED, use --hosts --[no-]debug DEPRECATED, use --log-level -x, --[no-]xml DEPRECATED - JUnit XML now generated by default --collect-perf-data Use sysstat on linux hosts to collect performance and load data ``` ## Options File Values ``` $ beaker --options-file additional_options.rb ``` The additional options file is provided with `--options-file /path/to/file.rb`. The file itself must contain a properly formatted Ruby hash. You can override any beaker internal option variable in the options file hash, but you have to associate the new value with the correct, internal key name. ### Example Options File ``` { :hosts_file => 'hosts.cfg', :ssh => { :keys => ["/Users/anode/.ssh/id_rsa-acceptance"], }, :timeout => 1200, :log_level => 'debug', :fail_mode => 'slow', :tests => [ 'tests/agent/agent_disable_lockfile.rb', 'tests/agent/fallback_to_cached_catalog.rb', ], :forge_host => 'api-forge-aio01-petest.puppetlabs.com', 'service-wait' => true, 'xml' => true, } ``` ## Default Values Values already included in Beaker as defaults for required arguments. ### Beaker Default Values ``` { :project => 'Beaker', :department => 'unknown', :created_by => ENV['USER'] || ENV['USERNAME'] || 'unknown', :host_tags => {}, :openstack_api_key => ENV['OS_PASSWORD'], :openstack_username => ENV['OS_USERNAME'], :openstack_auth_url => "#{ENV['OS_AUTH_URL']}/tokens", :openstack_tenant => ENV['OS_TENANT_NAME'], :openstack_keyname => ENV['OS_KEYNAME'], :openstack_network => ENV['OS_NETWORK'], :openstack_region => ENV['OS_REGION'], :jenkins_build_url => nil, :validate => true, :configure => true, :log_level => 'info', :trace_limit => 10, :"master-start-curl-retries" => 120, :masterless => false, :options_file => nil, :type => 'pe', :provision => true, :preserve_hosts => 'never', :root_keys => false, :quiet => false, :project_root => File.expand_path(File.join(File.dirname(__FILE__), "../")), :xml_dir => 'junit', :xml_file => 'beaker_junit.xml', :xml_time => 'beaker_times.xml', :xml_time_enabled => false, :xml_stylesheet => 'junit.xsl', :default_log_prefix => 'beaker_logs', :log_dir => 'log', :log_sut_event => 'sut.log', :color => true, :dry_run => false, :tag_includes => '', :tag_excludes => '', :timeout => 900, # 15 minutes :fail_mode => 'slow', :accept_all_exit_codes => false, :timesync => false, :disable_iptables => false, :set_env => true, :disable_updates => true, :repo_proxy => false, :package_proxy => false, :add_el_extras => false, :epel_url => "http://mirrors.kernel.org/fedora-epel", :epel_arch => "i386", :epel_7_pkg => "epel-release-7-6.noarch.rpm", :epel_6_pkg => "epel-release-6-8.noarch.rpm", :epel_5_pkg => "epel-release-5-4.noarch.rpm", :consoleport => 443, :pe_dir => '/opt/enterprise/dists', :pe_version_file => 'LATEST', :pe_version_file_win => 'LATEST-win', :host_env => {}, :host_name_prefix => nil, :ssh_env_file => '~/.ssh/environment', :profile_d_env_file => '/etc/profile.d/beaker_env.sh', :dot_fog => File.join(ENV['HOME'], '.fog'), :ec2_yaml => 'config/image_templates/ec2.yaml', :help => false, :collect_perf_data => 'none', :puppetdb_port_ssl => 8081, :puppetdb_port_nonssl => 8080, :puppetserver_port => 8140, :nodeclassifier_port => 4433, :cache_files_locally => true, :aws_keyname_modifier => rand(10 ** 10).to_s.rjust(10,'0'), # 10 digit random number string :run_in_parallel => [], :ssh => { :config => false, :verify_host_key => false, :auth_methods => ["publickey"], :port => 22, :forward_agent => true, :keys => ["#{ENV['HOME']}/.ssh/id_rsa"], :user_known_hosts_file => "#{ENV['HOME']}/.ssh/known_hosts", :keepalive => true } } ``` ## Priority of Settings Order of priority is as follows (from highest to lowest): 1. Environment variables are given top priority 1. Host/Config file options 1. `CONFIG` section of the hosts file 1. ARGV or Provided Arguments Array 1. Options file values 1. Default or Preset values are given the lowest priority ### Examples 1. If `BEAKER_PE_DIR` environment variable then any and all `pe_dir` settings in the host file, options file and beaker defaults are ignored 1. In this case, the `pe_dir` for `pe-ubuntu-lucid` will be `http://ubuntu/path`, while the `pe_dir` for `pe-centos6` will be `https://CONFIG/path`. ``` HOSTS: pe-ubuntu-lucid: roles: - agent - dashboard - database - master vmname : pe-ubuntu-lucid platform: ubuntu-10.04-i386 snapshot : clean-w-keys hypervisor : fusion pe_dir : http://ubuntu/path pe-centos6: roles: - agent vmname : pe-centos6 platform: el-6-i386 hypervisor : fusion snapshot: clean-w-keys CONFIG: nfs_server: none consoleport: 443 pe_dir: https://CONFIG/path ``` beaker-4.30.0/docs/concepts/beaker_libraries.md000077500000000000000000000044131407603575700214270ustar00rootroot00000000000000# Beaker Libraries Engineering at Puppet Labs has written several libraries that extends the functionality provided by Beaker. To learn how to create beaker libraries, see the [Beaker-Template](https://github.com/puppetlabs/beaker-template/blob/master/README.md) documentation. | Name | Description | Docs | |:-------------------|:--------------------------------------------------------------------|:----------------------------------------------------------------| | Master Manipulator | Easy DSL extension for changing configuration on a Puppet Master | [Github Repo](https://github.com/puppetlabs/master_manipulator) | | beaker_windows | Useful helpers for testing on Windows hosts | [Github Repo](https://github.com/puppetlabs/beaker_windows) | | Puppet Install Helper | Use environment variables for choosing which puppet version to install | [Github Repo](https://github.com/puppetlabs/beaker-puppet_install_helper) | | testmode_switcher | [prototype] run your puppet module tests in master/agent, apply or local mode | [Github Repo](https://github.com/puppetlabs/beaker-testmode_switcher) | | beaker-hostgenerator | Generates Beaker host files | [Github Repo](https://github.com/puppetlabs/beaker-hostgenerator/) | | beaker-answers | Generates answers for Puppet Enterprise installation | [Github Repo](https://github.com/puppetlabs/beaker-answers/) | | beaker-pe | Adds helper methods for Puppet Enterprise specific tasks | [Github Repo](https://github.com/puppetlabs/beaker-pe/) | | beaker-http | Adds ability to dispatch http traffic from the coordinator | [Github Repo](https://github.com/puppetlabs/beaker-http/) | | Beaker Rubymine Plugin | An IntelliJ IDEA plugin making Beaker test runs a native IDE experience | [Github Repo](https://github.com/samwoods1/BeakerRubyMinePlugin) | | 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/) | | beaker-puppet | Adds helper & install methods for Puppet-specific tasks | [Github Repo](https://github.com/puppetlabs/beaker-puppet/) beaker-4.30.0/docs/concepts/beaker_vs_beaker_rspec.md000066400000000000000000000067321407603575700226130ustar00rootroot00000000000000## How they relate beaker is an acceptance testing framework that is written in Ruby. beaker-rspec is a small shim that integrates the [beaker DSL](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL) (domain specific language) with [RSpec](http://rspec.info/) (a spec test format) and [Serverspec](http://serverspec.org/) (an extension to RSpec that provides many useful matchers). ![beaker vs. beaker-rspec (venn)](http://anodelman.github.io/shared/img/beaker_vs_beaker_rspec.jpg) ## beaker * provisions and configures test hosts * tests are just Ruby scripts * tests pass if they execute all the way through without errors/exceptions * tests can also use asserts to explicitly enforce a state * tests written in Ruby with Beaker's domain specific language extensions * tests can re-use functionality by using ruby constructs * tests are driven by Beaker's own test runner ### An example beaker test ### test_name "Be able to execute multi-line commands (#9996)" confine :except, :platform => 'windows' agents.each do |agent| temp_file_name = agent.tmpfile('9996-multi-line-commands') test_manifest = < "/bin/echo '#Test' > #{temp_file_name}; /bin/echo 'bob' >> #{temp_file_name};" } HERE expected_results = < true) expect(apply_manifest(pp, :catch_failures => true).exit_code).to be_zero end describe package(package_name) do it { is_expected.to be_installed } end describe service(service_name) do it { is_expected.to be_enabled } it { is_expected.to be_running } end describe port(80) do it { should be_listening } end end end ## How to choose? * Use the tool already in use in the project you are contributing to. * beaker-rspec is used by many modules inside and outside of Puppet Labs for system-level and acceptance tests. * beaker is used for all other testing within Puppet Labs (puppet, puppetDB, Puppet Enterprise, etc). beaker-4.30.0/docs/concepts/glossary.md000066400000000000000000000010731407603575700200010ustar00rootroot00000000000000# Glossary Most terms used in Beaker documentation should be common. The following documents project jargon which may otherwise be confusing. ## Coordinator 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)). ## System Under Test A System Under Test (SUT) is one of the systems which is the subject of testing with Beaker. Contrast the [Beaker Coordinator](#coordinator). beaker-4.30.0/docs/concepts/masterless_puppet.md000066400000000000000000000037341407603575700217230ustar00rootroot00000000000000# Overview ## What is masterless Puppet? 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). ## Why Would You Want to Do This? A few examples of common situations where running masterless Puppet would be useful are below: - Testing modules against Windows. Traditionally, a non-Windows master would be required, but is really just needless overhead in this case. - running Puppet to provision hosts, only running it the once, using `puppet agent`, and then providing it to your users ## How Do I Run Masterless? In order to have Beaker support a masterless Puppet setup, you have to do a few things: 1. include the `masterless: true` flag in the `CONFIG` section of your hosts file 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 3. Run Beaker just like you normally would # Under the Hood ## What is Beaker Doing by Default? 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. If no host was given with this role, then Beaker throws a `DSL::Outcomes::FailTest` Error, which causes that test case to fail. ## What Does This Flag Do? 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`. If you'd like to test both masterless and not, you'll have to deal with a role method potentially returning `nil`. ## How Do I Avoid Issues With This? You can make it so that a test will only run if we're not running masterless with this line: confine :to, :masterless => false and vice versa. beaker-4.30.0/docs/concepts/roles_what_are_they.md000066400000000000000000000030171407603575700221650ustar00rootroot00000000000000Each 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: on master, "echo hello" on database, "echo hello" ## Creating Your Own Roles Arbitrary role creating is supported in Beaker. New roles are created as they are discovered in the host/config file provided at runtime. ### Example User Role Creation ``` HOSTS: pe-ubuntu-lucid: roles: - agent - dashboard - database - master - nodes - ubuntu vmname : pe-ubuntu-lucid platform: ubuntu-10.04-i386 snapshot : clean-w-keys hypervisor : fusion pe-centos6: roles: - agent - nodes - centos vmname : pe-centos6 platform: el-6-i386 hypervisor : fusion snapshot: clean-w-keys CONFIG: nfs_server: none consoleport: 443 ``` 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. ``` on centos, 'echo I'm the centos box' on ubuntu, 'echo I'm the ubuntu box' on nodes, 'echo this command will be executed on both defined hosts' ``` beaker-4.30.0/docs/concepts/shared_options_for_executing_beaker_commands.md000066400000000000000000000031741407603575700272760ustar00rootroot00000000000000Beaker 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. ## :acceptable_exit_codes Provide either a single or an array of acceptable/passing exit codes for the provided command. Defaults to the single exit code `0`. on host, puppet( 'agent -t' ), :acceptable_exit_codes => [0,1,2] ## :accept_all_exit_codes Consider any exit codes returned by the command to be acceptable/passing. Defaults to `nil`/`false`. on host, puppet( 'agent -t' ), :accept_all_exit_codes => true ## :expect_connection_failure Assume that this command will cause a connection failure. Used for `host.reboot` so that Beaker can handle the broken ssh connection. on host, "reboot", {:expect_connection_failure => true} ## :dry_run Do not actually execute this command on the SUT. Defaults to `false`. on host, "don't do this crazy thing", {:dry_run => true} ## :pty Should this command be executed in a pseudoterminal? Defaults to `false`. on host, "sudo su -c \"service ssh restart\"", {:pty => true}) ## :silent Do not output any logging for this command. Defaults to `false`. on host, "echo hello", {:silent => true} ## :stdin Specifies standard input to be provided to the command post execution. Defaults to `nil`. on host, "this command takes input", {:stdin => "hiya"} ## [:run_in_parallel](../how_to/run_in_parallel.md) Execute the command against all hosts in parallel on hosts, puppet( 'agent -t' ), :run_in_parallel => true beaker-4.30.0/docs/concepts/style_guide.md000066400000000000000000000176621407603575700204660ustar00rootroot00000000000000# Beaker Style Guide ## Scope of this guide The purpose of this guide is to provide definitions for best practices when writing Beaker tests, both syntactically and stylistically. This guide will define and provide examples for preferred test layout and conventions. Common patterns that are recommended as well as patterns that should be avoided will be described. No style manual can cover every possible circumstance. When a judgement call becomes necessary, keep in mind the following general ideas: 1. **Readability matters**. If you have to choose between two equally effective alternatives, pick the more readable one. This is, of course, subjective, but if you can read your own code three months from now, that's a great start. Don't be clever over readable, unless you have a documented purpose. Use object oriented programming when things get complex and [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). 2. **Inherit upstream conventions**. Beaker is still ruby, so use the [ruby community style guide](https://github.com/bbatsov/ruby-style-guide). When not called out here, use the ruby style guide. ## Test Naming Tests should test what they say they test. Test names, both the name of the test file and the value given to the [`test_name`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#test_name-instance_method) function, should provide an accurate indication about the purpose of the test. The `test_name` function should be the first line in the Beaker test file. **Good:** ```ruby # head -1 puppet/acceptance/tests/resource/file/should_default_mode.rb test_name "file resource: set default modes" do ``` ## Structure Methods Should Use Explicit Blocks These methods aid in self-documenting your tests, including indention in the logs. If you don't use explicit blocks, beaker does not know how to properly indent your test's output. The most common [structure methods](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure) are [`#test_name`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#test_name-instance_method), [`#step`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#step-instance_method), and [`#teardown`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#teardown-instance_method). **Good:** ```ruby step 'do this thang' do on(host, "echo 'do this thang'") end ``` **Bad:** ```ruby step 'do this thang' on(host, "echo 'do this thang'") ``` ## Teardowns - Return the state of the system to the way it was prior to test execution - Put the teardown as early in the test as possible Teardowns must be used to return the system to the state it was in prior to the execution or attempted execution of the test. Beaker will gather all teardowns encountered throughout the execution path of the test. These teardowns will all be executed when the test exits, even if the test exits early due to a failure or error. ### Place Teardowns Early Teardowns can be placed anywhere in the test file or its helpers. The preferred style is to have a teardown step near the beginning of the test file to show the reader that the system state will be restored. **Good:** ```ruby test_name 'The source attribute' do target_file_on_nix = '/tmp/source_attr_test' teardown do hosts.each do |host| on(host, "rm #{target_file_on_nix}", :accept_all_exit_codes => true) unless host['platform'] =~ /^win/ end end ... end ``` Teardowns are at the mercy of the scoping of the variables necessary to perform the restoration of the system. This fact means that additional teardown steps will need to be added within the scope necessary to do their job. Effort should be taken to make the teardown steps prominent and readable so that it can be confirmed, via the logs, that the system has been restored. Teardown steps registered outside of tests should use [`#step`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#step-instance_method) to document and log what they are doing. ## Acceptable Exit Codes When using the Beaker [`on`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#on-instance_method) method, the default setting is that only an exit code of 0 (zero) will not trigger an error. When other exit codes are acceptable, the `:acceptable_exit_codes` key with an array of exit codes should be passed in the options hash to `#on`. If 0 (zero) is the only acceptable exit code, then the `:acceptable_exit_codes` symbol must not be used. **Good:** - Single 0 exit code allowed ```ruby on(host, "rm #{file_to_rm}") ``` - Single non-0 exit code allowed ```ruby on(host, "rm #{file_to_rm}", :acceptable_exit_codes => 1) ``` - Multiple exit codes allowed ```ruby on(host, "rm #{file_to_rm}", :acceptable_exit_codes => [0,1]) ``` - Any exit code allowed ```ruby on(host, "rm #{file_to_rm}", :accept_all_exit_codes => true) ``` In the last case, when any exit code is allowed, one must follow-up with a valid assertion test. If an exit_code outside of 0 is expected, one must use acceptable_exit_codes so the test will fail on the proper assertion and not error at that command. Allow only the minimum expected set of exit codes unless coverage is provided by subsequent assertions. ## Test Outcomes When to use each, and how to format the message: ### Expecting Failure If your tests are failing due to an "expected failure", you should wrap your failing assertion in an [`expect_failure`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#expect_failure-instance_method) block with an explanatory logging message: ```ruby expect_failure('expected to fail due to PE-1234') do assert_equal(400, response.code, 'bad response code from API call') end ``` Note that `expect_failure` will only trigger from failed assertions. It won't take care of failed host or `on` commands. To deal with expected failure from an `on` invocation, you'd want something more like this: ```ruby on(blah, 'blah', :allow_all_exit_codes => true) do |result| expect_failure 'known issue: TIK-1234' do assert_equal(4,result.exit_code,'did not receive expected exit code for blah') end end ``` ### `fail_test`, `pass_test`, & `skip_test` These can be used anywhere in a test to exit early. A [`skip_test`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#fail_test-instance_method) between two assertions, for instance, will run the first assertion, raise an exception for `skip_test`, run teardown, and then exit as we expect. The same is true for [fail_test](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#fail_test-instance_method). [`pass_test`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#fail_test-instance_method) is typically not required. When the end of a test is reached without causing an error (due to bad test code execution, or an unhandled exit code or exception) or failure (due to assertions), then it passes. `pass_test` can be used in a situation where one knows a test has passed before the end of a test under certain circumstances, such as during a loop that has not yet completed. ### Skipping Tests Skipping tests can be used, for instance, when they are temporarily failing or not yet complete. **Good:** ```ruby skip_test 'requires puppet and mcollective service scripts from AIO agent package' if @options[:type] != 'aio' ``` **Bad:** ```ruby confine :to, :platform => 'solaris:pending' ``` ## Confining Another way that you can skip or manipulate tests is by confining them to apply to a subset of the SUTs available for testing. Confining is a complex topic, and one we don't have the length to get into in the style guide. For an explanation of confining, as well as the best practices in using it, check out our [confine doc](../how_to/confine.md). ## Assertions Always include a _unique_ error message in your assertion statement. Use strict asserts whenever possible (e.g. assert_equal, assert_match. [More info](http://danwin.com/2013/03/ruby-minitest-cheat-sheet/)). beaker-4.30.0/docs/concepts/test_tagging.md000066400000000000000000000056421407603575700206230ustar00rootroot00000000000000## What is This? Beaker test tagging allows you to add tags to tests (using the [`tag` DSL method](http://www.rubydoc.info/github/puppetlabs/beaker/master/Beaker/DSL/TestTagging#tag-instance_method)), so that you can include or exclude a specific subset of the tests given for use in this run. Why would you want to use this? Here are some examples of what you can do with this functionality: - Run groups of tests separately from the same testing codebase - Declare different actions that should be taken when a test fails - Make new tests go through a provisional process before being considered solid tests ## How Tagging Works Add tags to a Beaker test at the beginning, like you would if you were using confine. Things to stay aware of: - A test that is not executed due to a tag will be considered a ‘skipped’ test - Tags are free form strings and will not be subjected to any correctness testing - Tags are NOT case sensitive - Tagging was added after Beaker 2.14.1. If you're using that version or older, this isn't available - `--test-tag-or` was added after Beaker 3.12.0. If you're using an older version, this isnt available ## Test Examples Single tag example: tag ‘long_running’ Multiple tag example: tag ‘long_running’, 'feature_test’ Preferred style block example: test_name “my test” do tag “filter1”,”filter2” … end Preferred style no-block example: test_name “my test” tag “filter1”,”filter2” ## Command Line Interaction `--test-tag-and`: Run the set of tests matching ALL of the provided single or comma-separated list of tags. `--test-tag-or`: Run the set of tests matching ANY of the provided single or comma-separated list of tags. `--test-tag-exclude`: Run the set of tests that do not contain ANY of the provided single or comma-separated list of tags. Beaker will raise an error if `--test-tag-and` & `--test-tag-exclude` contain the same tag, however. Beaker will also raise an error if you use both `--test-tag-or` & `--test-tag-and`, because it won't be able to determine which order they should be used in. ## CLI Examples Execute all ‘long_running’ tests. $ beaker --tests path/to/tests --test-tag-and long_running Execute all tests, except those that are ‘feature_test’ $ beaker --tests path/to/tests --test-tag-exclude feature_test Execute all tests that are long_running but not feature_test $ beaker --tests path/to/tests --test-tag-and long_running --test-tag-exclude feature_test Execute all tests marked both 'long_running' and 'feature_test' $ beaker --tests /path/to/tests --test-tag-and long_running,feature_test ## Environment Variable Support Equivalent to `--test-tag-and`: BEAKER_TEST_TAG_AND=long_running,feature_test Equivalent to `--test-tag-or`: BEAKER_TEST_TAG_OR=long_running,feature_test Equivalent to `--test-tag-exclude`: BEAKER_TEST_TAG_EXCLUDE=long_running,feature_test beaker-4.30.0/docs/concepts/testing_beaker_itself.md000066400000000000000000000036561407603575700225030ustar00rootroot00000000000000# Testing Beaker Itself 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. ## Testing Coverage ### Product Coverage 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. ### Platform Coverage 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. ## Test Suite Phases ### Beaker Spec The initial step in Beaker's pipeline is to execute spec testing with supported and future rubies; 2.2.5 and 2.3.1. ### Beaker Acceptance All acceptance tests use actual OS's with beaker installed and use beaker itself to verify that its own methods and classes are working. * The Base tests are tests that do not require puppet be installed on the SUT. This includes much of the DSL and host helpers. * The puppet tests rely on puppet being installed in the pre-suite ### Beaker Regression 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. beaker-4.30.0/docs/concepts/ticket_process.md000066400000000000000000000066271407603575700211710ustar00rootroot00000000000000# Ticket Process This doc is here to explain the lifecycle of a Beaker ticket. If you have any questions about the workflow of a Beaker ticket, or the stage that it's in, you should be able to answer them here. If not, then please let QE know, and we'll work to answer your question and update this doc, so that we can do this better going forward. Note that a typical Beaker ticket goes through these states. They can sometimes go in order, but on average, they loop around through the various stages, can cycle a number of times before becoming resolved, and will sometimes skip stages as well. ## Administrivia - Beaker tickets live in the [BKR project](https://tickets.puppetlabs.com/issues/?jql=project%20%3D%20BKR) - Create a [Jira account](http://tickets.puppetlabs.com) to interact with them ## Pre-Development States These are the states that a Beaker ticket goes through before it is picked up for development. Note that this is only for work done by the Beaker team. If there is something you'd like to contribute to, we would appreciate that you go through the development states for tracking purposes, but you can skip directly to the "In Progress" state if you're going to work on a ticket. ### Open The state for newly filed issues. These have not been triaged, and are waiting to be looked at by the team. ### Needs Information The state for blocked issues. These can be blocked by another issue (please link that issue from the ticket if that's the case), or they can be blocked on info needed from another party. If this is the case, please assign the ticket to the person you need info from to move the ticket forward. ### Accepted This is the state for when a ticket has been triaged, but hasn't yet been estimated for work from someone on the Beaker team, or someone ready to take on the work. Any preliminary information needed to understand the ticket (before investigation) should be gathered before this point. ### Ready for Engineering An accepted ticket that has been estimated should be put into the Ready for Engineering state. This does not necessarily mean that a ticket has been prioritized against other Beaker work, but prioritization should occur by the time the issue has been picked up in a sprint. ## Development States Once you're ready to pick up a ticket & work on it, then it should go through these states. ### In Progress This state is set aside for a Beaker contributor to let others know that they're currently working on a particular issue. ### Ready for Merge Once a Pull Request (PR) is generated for an issue, the contributor should set the status to Ready for Merge. We do have a GitHub integration setup, so if you've titled your PR correctly (according to our [contributor docs](/CONTRIBUTING.md)), it will be linked from the JIRA ticket. In the Beaker project, we leave the assignee as the person who wrote the proposed change, so that they know that they have to keep pushing for their code to be merged. A team member will review the PR. If changes are required, they will be communicated in the PR discussion on GitHub. ### Resolved Once your PR is merged, then you can Resolve your ticket. **NOTE** that when you do this, you should set the FixedVersion to BKR.next The reason that we do this now and not before is that we use this field to autogenerate our release notes. We want to make sure that we capture only work that is _in_ the next release, not work that's _intended_ for it. beaker-4.30.0/docs/concepts/types_puppet_4_and_the_all_in_one_agent.md000066400000000000000000000016031407603575700261400ustar00rootroot00000000000000## So What Are Types Anyway? 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. The two older types are represented by the `foss` and `pe` values. 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). ## New With Puppet 4: The AIO Type! 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. 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! beaker-4.30.0/docs/how_to/000077500000000000000000000000001407603575700152745ustar00rootroot00000000000000beaker-4.30.0/docs/how_to/archive_sut_files.md000066400000000000000000000050171407603575700213170ustar00rootroot00000000000000# How to Archive Files from the Systems Under Test (SUTs) 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). 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)). # How Do I Use This? `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. 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. # Challenges ## Conditionally Saving Files From SUTs 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. 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. # When Did This Come Out? `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). beaker-4.30.0/docs/how_to/change_terminal_output_coloring.md000066400000000000000000000023641407603575700242570ustar00rootroot00000000000000 Beaker uses a set of colors to output different types of messages on to the terminal. ## The Default Color Codes 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) ## Beaker Color Codes: 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) ## How to Customize: Changes to the default options can be made by editing the configuration file. Here are some examples: **Ex 1: Changing color of a particular type of message** 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) HOSTS: ... CONFIG: log_colors: success: "\e[01;35m" warn: "\e[00;33m" **Ex 2: Turning off colors.** The following option in the hosts file will print the whole output in one single color. HOSTS: ... CONFIG: color: false beaker-4.30.0/docs/how_to/cloning_private_repos.md000066400000000000000000000002711407603575700222110ustar00rootroot00000000000000# Cloning private repos 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 beaker-4.30.0/docs/how_to/confine.md000066400000000000000000000057761407603575700172560ustar00rootroot00000000000000# Confine ## How does it work? The confine method will limit the hosts the testcase is run against. You can pass in either :to or :except to control how the criteria is applied (**:to** will apply the criteria to the hosts in order to find a match, **:except** will apply the criteria to the hosts, and return those hosts that do not match). The default behaviour is that the **TestCase#hosts** array is modified to only contain the hosts that match (or don't match) the criteria. Full method documentation here: * [confine](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#confine-instance_method) ## How does the optional Array parameter work? However, if you pass in the optional Array to the method, the criteria will be applied to this hosts array (not **TestCase#hosts**). Subsequently, any of the hosts contained in **TestCase#hosts** that were not included in the Array passed to the method, will remain in **TestCase#hosts**. But any hosts that were filtered out by the criteria match will be overwritten. Take the following example: HOSTS: ubuntu_master: roles: - master - database - dashboard - classifier platform: ubuntu-14.04-amd64 hypervisor: vcloud template: Delivery/Quality Assurance/Templates/vCloud/ubuntu-1404-x86_64 centos7_agent: roles: - agent - frictionless platform: el-7-x86_64 template: Delivery/Quality Assurance/Templates/vCloud/centos-7-x86_64 hypervisor: vcloud ubuntu_agent: roles: - agent - frictionless platform: ubuntu-14.04-amd64 template: Delivery/Quality Assurance/Templates/vCloud/ubuntu-1404-x86_64 hypervisor: vcloud Using the following confine: `confine :to, { :platform => 'el-7-x86_64' }, agents` We will end up with a **TestCase#hosts** array with two hosts, one agent and one master. Since we passed in the 'agents' host array to the method, the criteria (:platform => 'el-7-x86_64') will be applied to this set of hosts. Only one agent matches this criteria (centos7_agent). Since the master host (ubuntu_master) was not included in the 'agents' host array when the criteria match was performed, it will remain in **TestCase#hosts**. But only one agent remains after the criteria match. `confine :except, { :platform => 'el-7-x86_64' }, agents` Will also return two hosts, one agent and one master. But it will be the other agent (ubuntu_agent) as its platform does not match 'el-7-x86_64'. `confine :to, { :platform => 'el-7-x86_64' }` Will return one host (centos7_agent). Because no Array was passed to the confine method, the criteria match is being applied directly to **TestCase#hosts** (which contains all hosts). `confine :except, { :platform => 'el-7-x86_64' }` Will return two hosts (ubuntu_master and ubuntu_agent). In order to limit the hosts to only the ubuntu agent, you would need to use: `confine :to, { :platform => 'ubuntu-14.04-amd64', :role => 'agent' }`beaker-4.30.0/docs/how_to/debug_beaker_tests.md000066400000000000000000000342661407603575700214520ustar00rootroot00000000000000# Debug beaker tests beaker includes [pry-byebug](https://github.com/deivid-rodriguez/pry-byebug), a gem that combines two powerful related tools: pry and byebug ### What is Pry? [Pry](http://pryrepl.org/) is a powerful Ruby editing and debugging tool. Beaker uses Pry runtime invocation to create a developer console. ### What is byebug? [Byebug](https://github.com/deivid-rodriguez/byebug) is a powerful debugger for ruby. It allows for flexible control of breakpoints, stepping through lines or the call stack. It lacks some features of pry such as source code editing and replaying code execution from within the debugging session, but can be used in combination with pry. There are several ways to have your tests break and enter debugging: 1. [Add a breakpoint to your test file](#add-a-breakpoint-to-your-test-file) 2. [Use the --debug-errors option](#use-the---debug-errors-option) 3. [Defining external breakpoints with byebug](#defining-external-breakpoints-with-byebug) ## Add a breakpoint to your test file Add Pry to individual tests by adding `require 'pry'` to the Ruby test file. Then add the statement `binding.pry` at the point in code where you want access to the full, current Beaker environment. ### Example #### Example test trypry.rb Here's a test file that exercises different ways of running commands on Beaker hosts. At the end of the main `hosts.each` loop I've included `binding.pry` to invoke the console. ``` hosts.each do |h| on h, "echo hello" if h['platform'] =~ /windows/ scp_to h, "beaker.gemspec", "/cygdrive/c/Documents\ and\ Settings/All\ Users/Application\ Data/" end on(h, "echo test block") do |result| puts "block result.stdout: #{result.stdout}" puts "block result.raw_stdout: #{result.raw_stdout}" end on(h, "echo test block, built in functions") do puts "built in function stdout: #{stdout}" puts "built in function stderr: #{stderr}" end result = on(h, "echo no block") puts "return var result.stdout: #{result.stdout}" puts "return var result.raw_stdout: #{result.raw_stdout}" binding.pry end ``` #### Sample output to the first `binding.pry` call: ``` $ bundle exec beaker --tests tests/trypry.rb --hosts configs/fusion/winfusion.cfg --no-provision { "project": "Beaker", ... "helper": [], "load_path": [], "pre_suite": [], "post_suite": [], "install": [], "modules": [], "logger": "#" } Hypervisor for pe-centos6 is none Hypervisor for w2k8r2 is none Hypervisor for w2k3r2 is none Beaker::Hypervisor, found some none boxes to create pe-centos6 10:55:27$ which curl /usr/bin/curl pe-centos6 executed in 0.14 seconds pe-centos6 10:55:27$ which ntpdate /usr/sbin/ntpdate pe-centos6 executed in 0.01 seconds w2k8r2 10:55:27$ which curl /bin/curl w2k8r2 executed in 0.42 seconds w2k3r2 10:55:27$ which curl /bin/curl w2k3r2 executed in 0.29 seconds No tests to run for suite 'pre_suite' Begin tests/trypry.rb pe-centos6 10:55:28$ echo hello hello pe-centos6 executed in 0.01 seconds pe-centos6 10:55:28$ echo test block test block pe-centos6 executed in 0.01 seconds block result.stdout: test block block result.raw_stdout: test block pe-centos6 10:55:28$ echo test block, built in functions test block, built in functions pe-centos6 executed in 0.00 seconds built in function stdout: test block, built in functions built in function stderr: pe-centos6 10:55:28$ echo no block no block pe-centos6 executed in 0.00 seconds return var result.stdout: no block return var result.raw_stdout: no block From: /Users/anode/beaker/tests/trypry.rb @ line 19 self.run_test: 14: 15: result = on(h, "echo no block") 16: puts "return var result.stdout: #{result.stdout}" 17: puts "return var result.raw_stdout: #{result.raw_stdout}" 18: => 19: binding.pry 20: 21: end [1] pry(#)> ``` #### Using the console At this point I have access to the console. I have full access to Beaker hosts, the Beaker DSL and Ruby. Here's some sample console calls: ``` [1] pry(#)> hosts => [pe-centos6, w2k8r2, w2k3r2] [2] pry(#)> on hosts[1], 'echo hello' w2k8r2 10:54:11$ echo hello hello w2k8r2 executed in 0.07 seconds => # [3] pry(#)> on hosts[1], 'ls /cygdrive/c/Documents\ and\ Settings/All\ Users/Application\ Data/' w2k8r2 10:56:15$ ls /cygdrive/c/Documents\ and\ Settings/All\ Users/Application\ Data/ Application Data Desktop Documents Favorites Microsoft Package Cache Start Menu Templates VMware beaker.gemspec ntuser.pol w2k8r2 executed in 0.09 seconds => # [4] pry(#)> result = on hosts[1], 'ls /cygdrive/c/Documents\ and\ Settings/All\ Users/Application\ Data/' w2k8r2 10:56:34$ ls /cygdrive/c/Documents\ and\ Settings/All\ Users/Application\ Data/ Application Data Desktop Documents Favorites Microsoft Package Cache Start Menu Templates VMware beaker.gemspec ntuser.pol w2k8r2 executed in 0.08 seconds => # [5] pry(#)> result.stdout.chomp => "Application Data\nDesktop\nDocuments\nFavorites\nMicrosoft\nPackage Cache\nStart Menu\nTemplates\nVMware\nbeaker.gemspec\nntuser.pol" ``` #### Continue regular test execution Simply `exit` the console. ``` [6] pry(#)> exit ``` ## Use the --debug-errors option You can run beaker and pass the option `--debug-errors` to have beaker enter the pry console if or when a test error occurs. This is useful for transient/intermittent errors where you may need to run the test a large number of times before seeing the failure. It's also useful to keep your hosts at the state where error occurred, as the test's teardown will not (yet) have executed. ### Example Consider this example test case _test.rb_, which is going to fail: test_name 'An important aspect of my product' important_expected_value = 3 step 'Assert expected matches actual' do actual_value = 2 assert_equal(important_expected_value, actual_value, 'This product is faulty') end You can run this test using `beaker -t test.rb` or (to use the `run` subcommand and just run the test and bypass host provisioning etc) `beaker run -t test.rb`. It fails. Now try `beaker run -t test.rb --debug-errors` This will enter a pry console when the failure occurs. * Assert expected matches actual Exception raised during step execution and debug-errors option is set, entering pry. Exception was: # HINT: Use the pry 'backtrace' and 'up' commands to navigate to the test code From: /home/puppet/code/beaker/lib/beaker/dsl/structure.rb @ line 51 Beaker::DSL::Structure#step: 38: def step step_name, &block 39: logger.notify "\n* #{step_name}\n" 40: set_current_step_name(step_name) 41: if block_given? 42: logger.step_in() 43: begin 44: yield 45: rescue Exception => e 46: if(@options.has_key?(:debug_errors) && @options[:debug_errors] == true) 47: logger.info("Exception raised during step execution and debug-errors option is set, entering pry. Exception was: #{e.inspect}") 48: logger.info("HINT: Use the pry 'backtrace' and 'up' commands to navigate to the test code") 49: binding.pry 50: end => 51: raise e 52: end 53: logger.step_out() 54: end 55: end [1] pry(#)> Because beaker is catching the exception, pry is showing you beaker code. But you can navigate to your own test code using the `backtrace` and `up` commands. `backtrace` shows output like: [1] pry(#)> backtrace --> #0 rescue in Beaker::DSL::Structure.rescue in step(step_name#String, &block#Proc) at /home/puppet/code/beaker/lib/beaker/dsl/structure.rb:51 #1 Beaker::DSL::Structure.step(step_name#String, &block#Proc) at /home/puppet/code/beaker/lib/beaker/dsl/structure.rb:43 #2 block in #>.block in run_test at /home/puppet/code/beaker/test.rb:5 I+--- #3 Kernel.eval(*args) at /home/puppet/code/beaker/lib/beaker/test_case.rb:133 #4 block in #>.block in run_test at /home/puppet/code/beaker/lib/beaker/test_case.rb:133 The arrow indicates pry is showing code at the top of the stack trace. At position #2 in the stack you can see line 5 of test.rb - this is the actual test code that failed. You can navigate there by entering the `up` command twice. From: /home/puppet/code/beaker/test.rb @ line 5 self.run_test: 1: test_name 'An important aspect of my product' 2: 3: important_expected_value = 3 4: => 5: step 'Assert expected matches actual' do 6: actual_value = 2 7: assert_equal(important_expected_value, actual_value, 'This product is faulty') 8: end [2] pry(#)> > **NOTE:** Bear in mind that though you are navigating through your code here; pry is not rewinding code execution. When you exit pry, beaker will continue its execution from the top of the backtrace, failing the test due to the error that occurred. From here, you can examine variables in your test. If you have hosts provisioned, you can send them commands using the [on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#on-instance_method) method, or in a separate terminal ssh into them to investigate their state at the point of failure. ## Defining external breakpoints with byebug This method is perhaps the least intuitive to use; but combines the advantages of letting you pick the line(s) of code to set breakpoints on; and being able to debug without needing to edit your test code directly. It is sometimes desirable to debug a test without changing the test's code at all; for example, if you want to compare a test at two or more different git commits, your test source needs to remain unmodified from its committed form. byebug allows you to externally define breakpoints to keep them separate from your source code. ### Example Consider the following test file test.rb: ```ruby test_name 'An important aspect of my product' important_expected_value = 3 step 'Assert expected matches actual' do actual_value = 2 assert_equal(important_expected_value, actual_value, 'This product is faulty') end ``` If you wanted to debug at line 6 to investigate the state of your System Under Test before the failing assertion executes, then create a file in your working directory named .byebugrc with contents: ``` break test.rb:7 ``` You can then run beaker within byebug like this: ``` [centos@test-development project]$ bundle exec byebug beaker -t test.rb [4, 13] in /home/puppet/code/beaker/vendor/bundle/ruby/2.3.0/bin/beaker 4: # 5: # The application 'beaker' is installed as part of a gem, and 6: # this file is here to facilitate running it. 7: # 8: => 9: require 'rubygems' 10: 11: version = ">= 0.a" 12: 13: if ARGV.first (byebug) ``` Note that byebug has immediately stopped at the first line of beaker and awaits your command. Type 'continue' to command byebug to continue execution until it next hits a breakpoint. ``` 8: => 9: require 'rubygems' 10: 11: version = ">= 0.a" 12: 13: if ARGV.first (byebug) continue Beaker! wWWWw |o o| | O | 3.18.0! |(")| / \X/ \ | V | | | | { "project": "Beaker", "department": "unknown", "created_by": "centos", "host_tags": {}, "openstack_api_key": null, "openstack_username": null, ... "timestamp": "2017-06-23 13:21:11 +0000", "beaker_version": "3.18.0", "log_prefix": "beaker_logs", "xml_dated_dir": "junit/beaker_logs/2017-06-23_13_21_11", "log_dated_dir": "log/beaker_logs/2017-06-23_13_21_11", "logger_sut": "#" } No tests to run for suite 'pre_suite' Begin test.rb An important aspect of my product * Assert expected matches actual Stopped by breakpoint 1 at test.rb:7 [1, 8] in test.rb 1: test_name 'An important aspect of my product' 2: 3: important_expected_value = 3 4: 5: step 'Assert expected matches actual' do 6: actual_value = 2 => 7: assert_equal(important_expected_value, actual_value, 'This product is faulty') 8: end (byebug) ``` You can now debug at this point in the file using [byebug commands](https://github.com/deivid-rodriguez/byebug#byebugs-commands) beaker-4.30.0/docs/how_to/enabling_cross_sut_access.md000066400000000000000000000020011407603575700230130ustar00rootroot00000000000000# Enabling access bewteen SUTs during an acceptance test run If you are running acceptance tests for Beaker that, at some point, will perform one of the following: * SSH between SUTs * Clone private repos You will need to run an SSH agent, and add the SSH key for accessing your SUTs/private repos, prior to running the tests. To load the SSH agent and add your SSH key, run the following: ~~~bash eval `ssh-agent` ssh-add ~~~ 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: ~~~bash 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 ~~~ And Beaker will be able to SSH between SUTs and clone private repos beaker-4.30.0/docs/how_to/hosts/000077500000000000000000000000001407603575700164345ustar00rootroot00000000000000beaker-4.30.0/docs/how_to/hosts/README.md000066400000000000000000000004651407603575700177200ustar00rootroot00000000000000# The Hosts Directory This directory contains docs explaining any peculiarities or details of a particular OS's host implementation. 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.beaker-4.30.0/docs/how_to/hosts/archlinux.md000066400000000000000000000031201407603575700207470ustar00rootroot00000000000000# Arch Linux > 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. Source: https://wiki.archlinux.org/index.php/Arch_Linux # Installation ## Specifying a version to install > Arch Linux strives to maintain the latest stable release versions of its software as long as systemic package breakage can be reasonably avoided. Source: https://wiki.archlinux.org/index.php/Arch_Linux Since Arch is a rolling release, the Puppet version installed by Pacman will always be the latest avaliable release from the upstream. 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. 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: ``` CONFIG: log_level: verbose type: aio ``` ## Versioning Arch doesn't really have the idea of a release version, as it's a rolling update. 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: ``` HOSTS: archlinux-2016.02.02-amd64: roles: - master platform: archlinux-2016.02.02-amd64 box: terrywang/archlinux box_version: 1.0.0 hypervisor: vagrant CONFIG: log_level: verbose type: aio ``` beaker-4.30.0/docs/how_to/hosts/cisco.md000066400000000000000000000041661407603575700200650ustar00rootroot00000000000000# Wind River Linux Wind River Linux is an embedded systems OS from Wind, an Intel Company. You can get more details on this from their [product page](http://www.windriver.com/products/linux/). Beaker provides support for 2 of Cisco's Wind River Linux platforms. Those platform codenames are `cisco_nexus` for Cisco NX-OS based systems and `cisco_ios_xr` for Cisco IOS XR based systems. Beaker currently can install puppet on Cisco Nexus and Cisco IOS XR. # Host Requirements WRLinux hosts validate their setup once created, and will fail if not setup correctly. There are two conditions that are validated specifically on WRLinux hosts. These conditions are listed below. A. All Cisco Nexus hosts will need a `:vrf` value, which determines their virtual routing framework for networking purposes. For our purposes, we tend to use the value `management`, so there is always a hosts file line that looks like this in our configuration: HOSTS: : ... vrf: management B. All Cisco hosts will also require a user to be set on the hosts. This is because they don't allow ssh'ing as the root user, which is one of the main assumptions that Beaker operates under in the usual case. In order to specify a user to ssh with, add this block to a host: HOSTS: : ... ssh: user: # Hypervisors WRLinux has only been developed and tested as a [vmpooler](https://github.com/puppetlabs/vmpooler) host. This doesn't mean that it can't be used in another hypervisor, but that Beaker doesn't specifically deal with the details of that hypervisor in creating WRLinux hosts, if there is anything specific to WRLinux that will need to be done in provisioning steps. # Installation Methods ## Open Source In order to install a puppet-agent against a WRLinux host, you'll have to use the [`install_puppet_agent_on`](blob/master/lib/beaker/dsl/install_utils/foss_utils.rb#L327) method. It reaches out to the WRLinux-specific host code for any information that it needs. You can check out [these methods](blob/master/lib/beaker/host/cisco.rb) if you need more information about this.beaker-4.30.0/docs/how_to/hosts/eos.md000066400000000000000000000017401407603575700175460ustar00rootroot00000000000000# EOS - Arista EOS is the network device OS from Arista. You can get more details from their [product page](https://www.arista.com/en/products/eos). # Hypervisors EOS has only been developed and tested as a [vmpooler](https://github.com/puppetlabs/vmpooler) host. This doesn't mean that it can't be used in another hypervisor, but that Beaker doesn't specifically deal with the details of that hypervisor in creating EOS hosts, if there is anything specific to EOS that will need to be done in provisioning steps. # Installation Methods ## Puppet Enterprise `install_pe` should "just work". ## Open Source In order to install a puppet-agent against an EOS host, you'll have to use the [`install_puppet_agent_dev_repo_on`](blob/master/lib/beaker/dsl/install_utils/foss_utils.rb#L1085) method. It reaches out to the EOS-specific host code for any information that it needs. You can check out [these methods](blob/master/lib/beaker/host/eos.rb) if you need more information about this.beaker-4.30.0/docs/how_to/hypervisors/000077500000000000000000000000001407603575700176715ustar00rootroot00000000000000beaker-4.30.0/docs/how_to/hypervisors/README.md000066400000000000000000000106211407603575700211500ustar00rootroot00000000000000# The Hypervisors Directory This directory contains docs explaining any peculiarities or details of a particular hypervisor's implementation. If you don't see a file here for a hypervisor, then it's either not yet documented (feel free to help us out here!), or it should conform to our normal hypervisor assumptions. # Credentials File Beaker uses credentials from a `.fog` file for authentication. This file came from using the [fog cloud services library](http://fog.io). Beaker now only uses fog functionality in the openstack hypervisor, but we still use the `.fog` file for a credentials store. By default, the file is located under the user's home directory. This helps to keep the credentials confidential. The path of `.fog` file can be changed by setting the `dot_fog` global beaker option. The `.fog` file is written in YAML. The keys are particular to the service that they correspond to, and each hypervisor's documentation should include the keys that are needed for it. An example `.fog` file is below: ```yaml default: vsphere_server: 'vsphere.example.com' vsphere_username: 'joe' vsphere_password: 'MyP@$$w0rd' vmpooler_token: 'randomtokentext' ``` Note: keys can be specified as either Strings or as Ruby Symbols (e.g. `:vsphere_server`). For interoprability with other systems, however, it is prudent to use Strings. The credentials file supports multiple sections. Hypervisors currently do not specify a section, and the normal behavior is to fall back to using the `default` section. You can override the section by specifying an environment variable, [as documented on the fog website](https://fog.io/about/getting_started.html). Set `ENV['FOG_CREDENTIAL']` to specify an alternative provider section and `Beaker::Shared::FogFileParser.parse_fog_file()` will attempt to load that section, no matter what other section the hypervisor specifies. # External Hypervisors Puppet and its community have made several gems that support different hypervisors with beaker, the reason for this is that we're looking to decrease Beaker's dependency footprint, and hypervisors are one of the places where we can often increase the load across all Beaker uses to benefit a small group that uses a particular hypervisor. In order to offset this, we've made a listing of gems and community-supported forks that support other external hypervisors. Please check them out if you'd like to use those hypervisors, hopefully it'll save you from spending time trying to support a new hypervisor yourself. ### Hypervisor gems made by puppet (pre-included in beaker 3.x): | Hypervisor | Fork | | :----------------------: | :---------------------------------------------------------: | | Vmpooler | [beaker-vmpooler](https://github.com/puppetlabs/beaker-vmpooler) | | Vcloud | [beaker-vcloud](https://github.com/puppetlabs/beaker-vcloud) | | AWS | [beaker-aws](https://github.com/puppetlabs/beaker-aws) | | Vagrant | [beaker-vagrant](https://github.com/puppetlabs/beaker-vagrant) | | VMware/Vsphere | [beaker-vmware](https://github.com/puppetlabs/beaker-vmware) | | Docker | [beaker-docker](https://github.com/puppetlabs/beaker-docker) | | Openstack | [beaker-openstack](https://github.com/puppetlabs/beaker-openstack) | | Google Compute | [beaker-google](https://github.com/puppetlabs/beaker-google) | ### beaker-abs There is another hypervisor made and used internally by puppet, [beaker-abs](https://github.com/puppetlabs/beaker-abs), but it isn't included in beaker 3.x. If you'd like to use beaker-abs, you'll have to include it yourself. You do that by requiring beaker-abs in your Gemfile as a sibling to beaker itself and then using `abs` as your hypervisor value in your hosts file. Please check the [beaker-abs README](https://github.com/puppetlabs/beaker-abs/blob/master/README.md) for more information. ### Hypervisor gems and beaker forks made by community: | Hypervisor | Fork | |:------------:|:--------------------------------------------------------------------:| | LXC | [Obmondo](https://github.com/Obmondo/beaker) | | DigitalOcean | [beaker-digitalocean](https://github.com/tiengo/beaker-digitalocean) | beaker-4.30.0/docs/how_to/hypervisors/solaris.md000066400000000000000000000006571407603575700216770ustar00rootroot00000000000000Used with `hypervisor: solaris`, the harness can connect to a Solaris host via SSH and revert zone snapshots. ### example .fog file ### :default: :solaris_hypervisor_server: solaris.example.com :solaris_hypervisor_username: harness :solaris_hypervisor_keyfile: /home/jenkins/.ssh/id_rsa-harness :solaris_hypervisor_vmpath: rpool/zoneds :solaris_hypervisor_snappaths: - rpool/ROOT/solaris beaker-4.30.0/docs/how_to/install_puppet.md000066400000000000000000000101671407603575700206660ustar00rootroot00000000000000# How To Install Puppet This doc will guide you through the process of installing Puppet Agent using beaker's DSL helpers. Note that this is not a complete documentation of the process, but a general overview. There will be specific hiccups for particular platforms and special cases. These are not all included at this point. The idea is that as we come upon them, we will now have a place to document those details, so that we can over time bring this to 100% completeness. As of Beaker 4.0, these DSL extensions have been moved to `beaker-puppet`, so you'll need to add that gem in your project and require it in your test cases. # First Things First: What Do You Want to Install? If you understand [beaker's roles](https://github.com/puppetlabs/beaker/blob/master/docs/concepts/roles_what_are_they.md) and just want the shortcuts to installing Open Source Puppet across your testing environment, then you should go to our "High Level Shortcuts" section below. If you'd like to only install Puppet Agents, please checkout our "Puppet Agent Installs" section for more information. # High Level Shortcuts The [`install_puppet_on`](http://www.rubydoc.info/gems/beaker/Beaker/DSL/InstallUtils/FOSSUtils#install_puppet_on-instance_method) method is a wrapper on our installing Puppet behavior that allows you to pass in which hosts in particular you'd like to install Puppet on as well as specifying the options used yourself. Please checkout the Rubydocs linked above for more info on this method. The [`install_puppet`](http://www.rubydoc.info/gems/beaker/Beaker/DSL/InstallUtils/FOSSUtils#install_puppet-instance_method) is deprecated. It's a shortcut method that just calls `install_puppet_on` passing the entire hosts array and global options hash. You can get the same using this code in your pre-suite: ```ruby install_puppet_on(hosts, options) ``` Note that both of the high level methods will call the `install_puppet_agent_on` method to install released Puppet Agent versions for your agent Systems Under Test (SUTs). Please checkout our "Released Open Source Puppet Agents" section below for more information on this method. # Puppet Agent Installs There are a number of Puppet Agents that you could be installing. There aren't only an ever-growing number of versions, but you can get Puppet Agent from a number of locations. If you'd like to install the Puppet Agent that comes with your particular Puppet Enterprise (PE) install, then please skip to our "PE Promoted Agent Installs" section below. For our different Open Source variants, check out the sections just below, which differentiate between released & development Puppet Agent versions. ### Released Open Source Puppet Agents To install a released version of Puppet Agent, beaker provides the [`install_puppet_agent_on`](http://www.rubydoc.info/gems/beaker/Beaker/DSL/InstallUtils/FOSSUtils#install_puppet_agent_on-instance_method) method. Please checkout the Rubydocs for more info on this method. ### Development Open Source Puppet Agents To install a development build of Puppet Agent, beaker provides the [`install_puppet_agent_dev_repo_on`](http://www.rubydoc.info/gems/beaker/Beaker/DSL/InstallUtils/FOSSUtils#install_puppet_agent_dev_repo_on-instance_method) method. Please checkout the Rubydocs for more info on this method. ### PE Promoted Agent Installs If you're using this method, then you're going to be downloading the installer from a URL configured like so: ```ruby http://pm.puppetlabs.com/puppet-agent/#{ pe_version }/#{ puppet_agent_version }/repos ``` `pe_version` is a variable that you can provide using either the host or global property `:pe_ver`. This is usually done in the hosts file, and will default to `4.0.0-rc1` if nothing is specified. `puppet_agent_version` is a variable you can provide the value of through the same methods as `pe_version` above. It will default to `latest`. Beaker's DSL method to install from this location is [`install_puppet_agent_pe_promoted_repo_on`](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/InstallUtils/FOSSUtils#install_puppet_agent_pe_promoted_repo_on-instance_method). Follow the link to get API-level docs on this method for more info.beaker-4.30.0/docs/how_to/platform_specific_tag_confines.md000066400000000000000000000054421407603575700240330ustar00rootroot00000000000000# Platform-Specific Tag Confines ## What Are These? Typically when adding support for new platforms, a number of tests have to be confined away from executing on that new platform. This can be for a number of reasons, from partial implementation/support to tests having incorrect assumptions that don't work on the new platform. Platform-specific tag confines are structures created to make this workflow easier, & achievable in a much more low-impact and adaptable way. ## Ok, So How Do We Use Them? In the local options file (provided to the command line interface (CLI) using the `--options-file` parameter), you can now provide an array of hashes, where each hash specifies a platform to confine, based on the tags included in the test. The local options file key is `:platform_tag_confines`. An example local options file is included below (remember that local options files have to be readable into beaker as a ruby hash): ```ruby { :platform_tag_confines => [ { :platform => /^ubuntu-1404/, :tag_reason_hash => { "metrics" => "Can't do this, because bananas are in the field", "ui" => "TODO: We have not applied the UI tests to Ubuntu yet", } }, { :platform => /^centos-7/, :tag_reason_hash => { "database" => "WUT is this system doing? I dunno, must skip", "long_running" => "Flakiest test EVA. Would not run against centos-7, will kill...", "ui" => "CentOS reason", } } ] } ``` In this case, there are two platform confines objects specified, one for Ubuntu 14.04, and the other for CentOS 7. These objects consist of a hash, filled with two entries: the `:platform` regex, and the `:tag_reason_hash`. The `:platform` regex is just that, a Ruby regex matching the host's platform string. The `:tag_reason_hash` is another hash that maps tags to the reason that tests that have this particular tag are being confined away from testing. Taking one of our confine examples from above, we can think of the Ubuntu UI confine example like this: "ui" tests will be confined away from ubuntu-1404 hosts, because "TODO: We have not applied the UI tests to Ubuntu yet" ## But Why Do We Need These? Usually when we add platforms, the confining tests step is very heavy handed & repetitive, usually consisting of adding boilerplate confine calls that don't provide any explanation for why these are happening. This can make it hard to later know the reason for the confine, making it hard to know when we should be able to remove confines down the road. This new workflow should provide a number of advantages over the previous one, including: 1. Always reporting a reason for confining a platform 2. Allowing these to be dynamically determined from configuration, rather than needing to edit tens to hundreds of test files in-repo beaker-4.30.0/docs/how_to/preserve_hosts.md000066400000000000000000000114741407603575700207000ustar00rootroot00000000000000# How to Preserve Hosts ## Motivation Often when developing acceptance tests to be run with beaker, you'll want to be able to quickly iterate on those tests in an already setup System Under Test (SUT) configuration. Beaker provides the ability to do that using its preserved hosts functionality. ## But Is This The Right Solution? Many people are of the opinion that if you have to login to a SUT, then you've already failed, and that the purpose of good automation is so that you don't ever have to do this. Beaker supports a diverse group of developers and testers, and we try to remain flexible and support multiple approaches to these problems. Another approach you might take to debugging failed tests, during development or test runs in your Continuous Integration (CI) systems, is to archive any artifacts or log files from the SUTs, so that you can read them all from the beaker coordinator. Beaker provides a Domain-Specific Language (DSL) method to accomodate this workflow, called `archive_file_from`. Check out the [how-to article](archive_sut_files.md) on this method for details on it. That being said, the preserved hosts functionality is still useful, particularly for new development. In this case, you might not be able to necessarily tell what files you might need from your SUTs, and exploration might be necessary. ## How Do I Use It? Note that where you decide to use this option will affect its precedence. You can learn more about this in our [argument processing & precedence doc](../concepts/argument_processing_and_precedence.md). ### Command-Line Option The primary way that this option is provided is through the command line, where it's represented by the `--preserved-hosts` option. It defaults to `never`, and if you run `beaker --help`, you'll see the range of values that can be taken: $ beaker --help ... --preserve-hosts [MODE] How should SUTs be treated post test Possible values: always (keep SUTs alive) onfail (keep SUTs alive if failures occur during testing) onpass (keep SUTs alive if no failures occur during testing) never (cleanup SUTs - shutdown and destroy any changes made during testing) (default: never) ... ### Local Options or Global Config You can also provide a value for this option wherever you can provide a value that gets merged into beaker's global options hash. The two ways to do this are to provide it in a local options file, or in the global `CONFIG` section of your hosts file. To learn more about how to set these options, check out the [Options File Values](../concepts/argument_processing_and_precedence.md#options-file-values) and the [`CONFIG` Section of a Hosts File](../concepts/argument_processing_and_precedence.md#config-section-of-hosts-file) sections of the [argument processing & precedence doc](../concepts/argument_processing_and_precedence.md). Either way, the underlying property name that you will have to set if you use these options is `:preserve_hosts`. ## What Does It Do (In Detail)? Using the preserved hosts functionality outputs a `hosts_preserved.yml` file into the directory that holds all of the log files for a particular beaker run. The short way to access that directory is to look into the `log/latest` path, which is a symlink to the actual log directory for the run. The `hosts_preserved.yml` file generated by a run differs from the original hosts file passed into Beaker in a number of ways: - all suites are emptied - the host names are updated to point to the IPs (or hostnames) of the VMs since they exist and no longer need provisioning - the `provision` option is set to `false` (check [Beaker Test Run's](../tutorials/test_run.md) provisioning section for details). Beaker will also output the lines included below at the end of the run: You can re-run commands against the already provisioned SUT(s) with: /.../beaker --hosts log/centos7-64mdac-ubuntu1504-64a/2016-07-18_13_42_44/hosts_preserved.yml --tests smoke_simple.rb --preserve-hosts onpass The important piece here is the hosts argument, in that it's informing you of where the preserved hosts file is located. Of course, until you kick off another beaker run, you can also reach this file with the `log/latest/hosts_preserved.yml` symlink, as mentioned at the beginning of this section. ## What Do I Do With It? For subsequent runs, if you want to set something, you'll have to pay attention to the precedence of your arguments, to be sure that you are overriding what is set in the preserved hosts file. Checkout the [argument processing & precedence doc](../concepts/argument_processing_and_precedence.md) for more details on the precedence order of options parsing.beaker-4.30.0/docs/how_to/rake_tasks.md000066400000000000000000000034061407603575700177500ustar00rootroot00000000000000# Rake test tasks for running beaker There are some rake tasks that you can use to run Beaker tests from your local project dir. To use them from within your own project, you will need to require the following file in your project's rakefile: require 'beaker/tasks/test' You will also need to have Beaker installed as part of your bundle. When you run: rake --tasks from your project dir, you should see (as well as any rake tasks you have defined locally) rake beaker:test[hosts,type] # Run Beaker Acceptance rake beaker:test:git[hosts] # Run Beaker Git tests rake beaker:test:pe[hosts] # Run Beaker PE tests The last two tasks assume that you have an options file in `/acceptance` named `beaker-git.cfg` and `beaker-pe.cfg` respectively. Your options file would look something like: { :type => 'git', :pre_suite => ['./acceptance/setup/install.rb'], :hosts_file => './acceptance/config/windows-2012r2-x86_64.cfg', :log_level => 'debug', :tests => ['./acceptance/tests/access_rights_directory', './acceptance/tests/identity', './acceptance/tests/owner', './acceptance/tests/propagation', './acceptance/tests/use_cases', './acceptance/tests/access_rights_file', './acceptance/tests/group', './acceptance/tests/inheritance', './acceptance/tests/parameter_target', './acceptance/tests/purge'], :keyfile => '~/.ssh/id_rsa-acceptance', :timeout => 6000 } To use the more generic test task, you will need to pass in the type as the 2nd argument to the rake task: rake beaker:test[,smoke] This will assume that you have created the file: acceptance/beaker-smoke.cfg beaker-4.30.0/docs/how_to/recipes.md000066400000000000000000000011531407603575700172500ustar00rootroot00000000000000# What is This? Patterns for best-use solutions to (not so) common problems ## How do i set persistent environment variables on a SUT, such as PATH? ```ruby host.add_env_var('PATH', '/opt/puppetlabs/bin:$PATH') ``` ## How do i run commands on a SUT as a non-root user? (warning) this should be abstracted into a beaker helper, or part of `on()`: BKR-168 - Beaker::DSL::Helpers needs "as" method READY FOR ENGINEERING Create the user, then `su` with `--command`: ```ruby on(host, puppet("resource user #{username} ensure=present managehome-true")) on(host, "su #{username} --command '#{command}'") ```beaker-4.30.0/docs/how_to/run_in_parallel.md000066400000000000000000000037361407603575700207750ustar00rootroot00000000000000# run_in_parallel global and command options ## run_in_parallel global option 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: ```console $ export BEAKER_RUN_IN_PARALLEL=configure,install ``` Including 'configure' causes timesync to execute in parallel (if timesync=true for any host) Including 'install' causes as much of the puppet install to happen in parallel as possible. ## run_in_parallel command option 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: - on - run_block_on - block_on - install_puppet_agent_on - apply_manifest_on - stop_agent_on - execute_powershell_script_on ## Using InParallel in your test scripts In addition to the options, you can use InParallel within your test scripts as well. Examples: ```ruby include InParallel test_name('test_test') # Example 1 hosts.each_in_parallel{ |host| # Do something on each host } def some_method_call return "some_method_call" end def some_other_method_call return "some_other_method_call" end # Example 2 # Runs each method within the block in parallel in a forked process run_in_parallel{ @result = some_method_call @result_2 = some_other_method_call } # results in 'some_method_callsome_other_method_call' puts @result + @result_2 ``` **_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. Further documentation on the usage of [InParallel](http://github/puppetlabs/in-parallel/readme.md) beaker-4.30.0/docs/how_to/ssh_agent_forwarding.md000066400000000000000000000017421407603575700220170ustar00rootroot00000000000000# How to Forward ssh(1) Agent `ssh(1)` agent forwarding can is activated in the `CONFIG` section of the hosts file: ```yaml HOSTS: ... CONFIG: forward_ssh_agent: true ``` 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. A dirty hack is to `chmod -R 777 /tmp/ssh-*` before changing to another user and relying on `$SSH_AUTH_SOCK`. Example: ```puppet exec { '/bin/chmod -R 777 /tmp/ssh-*': } -> vcsrepo { '/var/www/app': provider => 'git', source => 'https://example.com/git/app.git', user => 'deploy' } ``` ## Cross-SUT access 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 beaker-4.30.0/docs/how_to/ssh_connection_preference.md000066400000000000000000000042631407603575700230350ustar00rootroot00000000000000# Set SSH connection methods preference Setting up a SSH connection to hosts can be tricky. Beaker supports three methods to SSH to hosts: 1. `:ip` 2. `:vmhostname` - DNS name 3. `:hostname` 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. ## Why set a preference? 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. ## Setting SSH connection preference at hypervisor level 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. For example, `beaker-vmpooler` overriding this in [vmpooler.rb](https://github.com/puppetlabs/beaker-vmpooler/blob/master/lib/beaker/hypervisor/vmpooler.rb) file. ## Setting SSH connection methods in hosts file 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. Example of a host file: ```yaml HOSTS: ubuntu1604-64-1: hypervisor: vmpooler platform: ubuntu-16.04-amd64 template: ubuntu-1604-x86_64 ssh_preference: - :vmhostname - :hostname - :ip roles: - agent - default ubuntu1604-64-2: hypervisor: vmpooler platform: ubuntu-16.04-amd64 template: ubuntu-1604-x86_64 ssh_preference: [:ip, :vmhostname] roles: - agent CONFIG: nfs_server: none consoleport: 443 ``` beaker-4.30.0/docs/how_to/test_arbitrary_beaker_versions.md000066400000000000000000000025431407603575700241210ustar00rootroot00000000000000# Test arbitrary beaker versions without modifying test code 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: ```ruby def location_for(place, fake_version = nil) if place =~ /^(git[:@][^#]*)#(.*)/ [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else [place, { :require => false }] end end ``` Once this method definition is in place in the Gemfile, we can call it in a gem command, like this: ```ruby gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '~> 2.0') ``` ## Example BEAKER_VERSIONs ### git locations ``` git@github.com:puppetlabs/beaker.git#master git://github.com/puppetlabs/beaker.git#master ``` ### file locations ``` file://../relative/path/to/beaker ``` 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. beaker-4.30.0/docs/how_to/the_beaker_dsl.md000066400000000000000000000252231407603575700205550ustar00rootroot00000000000000# The Beaker DSL The Beaker [Domain-Specific Language (DSL)](https://en.wikipedia.org/wiki/Domain-specific_language) is a set of Ruby convenience methods provided by Beaker to make testing easier. Beaker maintains [yard documentation](http://www.rubydoc.info/github/puppetlabs/beaker/) covering the DSL to help you use it. That documentation can sometimes be difficult to navigate, however, so this doc has been created to help you find your way around. ## DSL Caveats Note that if you're using a beaker-library, any methods provided there won't be documented here. You can refer to the [beaker-libraries listing doc](../concepts/beaker_libraries.md) for links to those projects which should include their own documentation. Another common point of confusion about the Beaker DSL is that there is a similar set of methods that come along in Host objects themselves. You can tell these methods apart in a test by their invocation method. Host methods are Ruby instance methods on Host objects, so they'll be invoked on a Host object like so: ```ruby host.host_method_name(host_method_params) ``` and they'll know by default which hosts to act on because you've provided them that through choosing which hosts to call them on. Beaker DSL methods are called in the wider context of the test itself, however, and often need to be passed the hosts you'd like them to act on: ```ruby on(hosts, "cowsay 'the tortoise lives in agony'") ``` Another way you can tell them apart is their location in the codebase, & thus in the Rubydocs. Beaker DSL methods live under the [Beaker::DSL module](https://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL), whereas Host methods are all included in the [Beaker::Host object itself](https://www.rubydoc.info/github/puppetlabs/beaker/Beaker/Host). Follow that link to the Host Rubydoc and checkout the Instance Method Summary to see a listing of Host methods. Note that they won't be listed here though. ## Assertions To be used for confirming the result of a test is as expected. Beaker include all Minitest assertions, plus some custom built assertions. * [Minitest assertions](http://docs.seattlerb.org/minitest/Minitest/Assertions.html) * [assert_output](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Assertions#assert_output-instance_method) * [assert_no_match](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Assertions#assert_no_match-instance_method) ## Helpers DSL methods designed to help you interact with hosts (like running arbitrary commands on them) or interacting with the web (checking is a given URL is alive or not). ### Host DSL methods for host manipulation. * [on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#on-instance_method) * [shell](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#shell-instance_method) * [stdout](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#stdout-instance_method) * [stderr](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#stderr-instance_method) * [exit_code](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#exit_code-instance_method) * [scp_from](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#scp_from-instance_method) * [scp_to](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#scp_to-instance_method) * [rsync_to](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#rsync_to-instance_method) * [deploy_package_repo](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#deploy_package_repo-instance_method) * [create_remote_file](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#create_remote_file-instance_method) * [run_script_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#run_script_on-instance_method) * [run_script](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#run_script-instance_method) * [install_package](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#install_package-instance_method) * [check_for_package](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#check_for_package-instance_method) * [upgrade_package](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#upgrade_package-instance_method) * [add_system32_hosts_entry](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#add_system32_hosts_entry-instance_method) * [backup_the_file](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#backup_the_file-instance_method) * [curl_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#curl_on-instance_method) * [curl_with_retries](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#curl_with_retries-instance_method) * [retry_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#retry_on-instance_method) * [run_cron_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#run_cron_on-instance_method) * [create_tmpdir_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#create_tmpdir_on-instance_method) As of Beaker 4.0, moved to `beaker-puppet`. See [upgrade_from_3_to_4.md](upgrade_from_3_to_4.md). * [echo_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/HostHelpers#echo_on-instance_method) ### Web Helpers for web actions. * [port_open_within?](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/WebHelpers#port_open_within?-instance_method) * [link_exists?](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/WebHelpers#link_exists?-instance_method) * [fetch_http_file](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/WebHelpers#fetch_http_file-instance_method) * [fetch_http_dir](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/WebHelpers#fetch_http_dir-instance_method) ### Test DSL methods for setting information about the current test. * [current_test_name](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/TestHelpers#current_test_name-instance_method) * [current_test_filename](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/TestHelpers#current_test_filename-instance_method) * [current_step_name](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/TestHelpers#current_step_name-instance_method) * [set_current_test_name](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/TestHelpers#set_current_test_name-instance_method) * [set_current_test_filename](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/TestHelpers#set_current_test_filename-instance_method) * [set_current_step_name](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Helpers/TestHelpers#set_current_step_name-instance_method) ## Outcomes Methods that indicate how the given test completed (fail, pass, skip or pending). * [fail_test](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#fail_test-instance_method) * [pass_test](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#pass_test-instance_method) * [pending_test](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#pending_test-instance_method) * [skip_test](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#skip_test-instance_method) * [formatted_message](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Outcomes#formatted_message-instance_method) ## Patterns Shared methods used as building blocks of other DSL methods. * [block_on](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Patterns#block_on-instance_method) ## Roles DSL methods for accessing hosts of various roles. * [agents](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#agents-instance_method) * [master](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#master-instance_method) * [database](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#database-instance_method) * [dashboard](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#dashboard-instance_method) * [default](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#default-instance_method) * [not_controller](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#not_controller-instance_method) * [agent_only](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#agent_only-instance_method) * [aio_version?](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#aio_version?-instance_method) * [aio_agent?](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#aio_agent?-instance_method) * [add_role](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#add_role-instance_method) * [add_role_def](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#add_role_def-instance_method) * [any_hosts_as?](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#any_hosts_as?-instance_method) * [hosts_as](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#hosts_as-instance_method) * [find_host_with_role](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#find_host_with_role-instance_method) * [find_only_one](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#find_only_one-instance_method) * [find_at_most_one](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Roles#find_at_most_one-instance_method) ## Structure DSL methods that describe and define how a test is executed. * [step](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#step-instance_method) * [test_name](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#test_name-instance_method) * [teardown](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#teardown-instance_method) * [expect_failure](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#expect_failure-instance_method) * [confine](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#confine-instance_method) * [confine_block](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#confine_block-instance_method) * [tag](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#tag-instance_method) * [select_hosts](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#select_hosts-instance_method) * [inspect_host](http://www.rubydoc.info/github/puppetlabs/beaker/Beaker/DSL/Structure#inspect_host-instance_method) beaker-4.30.0/docs/how_to/upgrade_from_2_to_3.md000066400000000000000000000106451407603575700214430ustar00rootroot00000000000000# How To Upgrade from 2.y to 3.0 This is a guide detailing all the issues to be aware of, and to help people make any changes that you might need to move from beaker 2.y to 3.0. To test out beaker 3.0.0, we recommend implementing the strategy outlined [here](test_arbitrary_beaker_versions.md) to ensure this new major release does not break your existing testing. ## Ruby version 1.9.3 no longer supported Official support for 1.9.3 has been eol'd since Feb 2015; the beaker 3.0.0 release drops support for ruby 1.9.3 and will not install with ruby 1.9.3. We suggest using ruby >= 2.2.5, as that is the version we currently test and support at Puppet. ## Locally Cached Files This is a change of the `:cache_files_locally` preset from `true` to `false`. At this time, the `:cache_files_locally` setting only affects the [`fetch_http_file` method](https://github.com/puppetlabs/beaker/blob/master/lib/beaker/dsl/helpers/web_helpers.rb#L44). This is an internal method used in both Puppet Enterprise (PE) and Open Source Puppet install helpers to download files from the internet to the Beaker coordinator. If a file with the same destination name already exists on the coordinator, Beaker would not fetch the file and use the cached copy instead. In general, this wasn't a big problem because we typically have our version numbers in our install artifacts, so file name matching is enough. In our Windows MSI installers, however, we would many times not have versions built into the file name. Since that's the case, you could get an old version installed because it was already on your coordinator filesystem. The `:cache_files_locally` setting allows you to set whether you want to use a local file cache, or get fresh installers every time. This setting is now set to false, and will get installers from the online source every time. If you'd like to keep this setting the way it was in 2.y, then just set the global option `:cache_files_locally` to `false`. Checkout the [Argument Processing and Precedence](../concepts/argument_processing_and_precedence.md) doc for info on how to do this. ## EPEL package update In beaker < 3.0.0, the epel package names had hardcoded defaults listed in the presets default; in beaker >= 3.0.0, beaker utilizes the `release-latest` file provided on epel mirrors for el versions 5, 6, and 7. Since only the latest epel packages are available on epel mirrors, beaker only supports installation of that latest version. ## Solaris and AIX Hypervisors removed Special cased hypervisor support for Solaris and AIX have been removed in favor of a `hypervisor=none` workflow where the provisioning of SUTs is handled separately outside of beaker itself. Solaris and AIX are still of course supported as `platform` strings; only these special-cased hypervisors have been removed. ## Environment Variable DSL Methods In [BKR-914](https://tickets.puppetlabs.com/browse/BKR-914) we fixed our host methods that deal with environment variables ( [#add_env_var](http://www.rubydoc.info/github/puppetlabs/beaker/Unix/Exec#add_env_var-instance_method), [#get_env_var](http://www.rubydoc.info/github/puppetlabs/beaker/Unix/Exec#get_env_var-instance_method), and [#clear_env_var](http://www.rubydoc.info/github/puppetlabs/beaker/Unix/Exec#clear_env_var-instance_method)). Before, these methods used regular expressions that were too loose. This means that in an example of a call like `get_env_var('abc')`, the environment variables `abc=123`, `xx_abc_xx=123`, and `123=abc` would all be matched, where the intent is to get `abc=123` alone. From Beaker 3.0 forward, this will be the case. ## beaker-pe Import Changes Starting in beaker 3.0, there is no explicit beaker-pe requirement in beaker. This separates the two, meaning that you'll have to explicitly require beaker-pe if you do need it in your testing. And if you don't need it, you won't get it, limiting your dependencies & exposure to unnecessary code. Luckily, if you do need it, this shouldn't be hard to update. These are the steps needed to use beaker-pe with beaker 3.0: 1. put a dependency on beaker-pe in your Gemfile as a sibling to your beaker requirement (make sure beaker-pe is >= 1.0) 2. That's it! Beaker itself will still `require 'beaker-pe'`, so making sure that it is specified in your project's Gemfile is the only code change you will need to make. Please note that this is only supported with the `beaker-pe` gem; other beaker libraries will need an explicit `require` in your test setup. beaker-4.30.0/docs/how_to/upgrade_from_3_to_4.md000066400000000000000000000077711407603575700214530ustar00rootroot00000000000000# How To Upgrade from 3.y to 4.0 This is a guide detailing all the issues to be aware of, and to help people make any changes that you might need to move from beaker 3.y to 4.0. To test out beaker 4.0.0, we recommend implementing the strategy outlined [here](test_arbitrary_beaker_versions.md) to ensure this new major release does not break your existing testing. ## PE Dependency In Beaker 3.0.0, `beaker-pe` was removed as a dependency. A mechanism to automatically include that module, if available, was added for convenience and to ease the transition. That shim has been removed. As of 4.0, you will have to explicitly require `beaker-pe` alongside `beaker` as a dependency in your project if you need it in your tests. You'll also need to add `require 'beaker-pe'` to any tests that use it. ## `PEDefaults` and `#configure_type_defaults_on` PEDefaults has been moved to `beaker-pe`. The call to `#configure_type_defaults_on` that was previously made in `#set_env` is no longer made. You will now need to explicitly call `#configure_type_defaults_on` in your tests when needed. ## Puppet Dependency Just like `beaker-pe` was removed as a dependency in 3.0, we have removed `beaker-puppet` as a dependency in 4.0. This means that you will have to explicitly require `beaker-puppet` alongside `beaker` as a dependency in your project if you need it in your tests. You'll also need to add `require 'beaker-puppet'` to any of your tests that use it. ## Hypervisor Loading We have also removed the explicit dependency on all previously-included hypervisor libraries. Don't worry, the transition should be easy. In order to use a specific hypervisor or DSL extension library in your project, you will need to include them alongside Beaker in your Gemfile or project.gemspec. E.g. ~~~ruby # Gemfile gem 'beaker', '~>4.0' gem 'beaker-aws' # project.gemspec s.add_runtime_dependency 'beaker', '~>4.0' s.add_runtime_dependency 'beaker-aws' ~~~ Beaker will automatically load the appropriate hypervisors for any given hosts file, so as long as your project dependencies are satisfied there's nothing else to do. No need to `require` this library in your tests. Simply specify `hypervisor: hypervisor_name` in your hosts file. The following hypervisor libraries were removed in 4.0: - [beaker-abs](https://github.com/puppetlabs/beaker-abs) - [beaker-aws](https://github.com/puppetlabs/beaker-aws) - [beaker-docker](https://github.com/puppetlabs/beaker-docker) - [beaker-google](https://github.com/puppetlabs/beaker-google) - [beaker-openstack](https://github.com/puppetlabs/beaker-openstack) - [beaker-vagrant](https://github.com/puppetlabs/beaker-vagrant) - [beaker-vcloud](https://github.com/puppetlabs/beaker-vcloud) - [beaker-vmpooler](https://github.com/puppetlabs/beaker-vmpooler) - [beaker-vmware](https://github.com/puppetlabs/beaker-vmware) For acceptance testing, beaker-vmpooler, beaker-aws, and beaker-abs have been retained as development dependencies. These will be removed as the CI pipelines is upgraded, so *do not rely on them being there for your project*. ## `Host.rsync_to` and Rsync-dependent Methods The `Host.rsync_to()` method has been overhauled, fixing a number of lingering issues and edge cases including [BKR-462](http://tickets.puppetlabs.com/browse/BKR-462) and [BKR-463](http://tickets.puppetlabs.com/browse/BKR-463). When a call to `rsync_to` fails for any reason, Beaker will throw `CommandFailure` as standard, including an error message from the Rsync library. The two primary causes for previously silent Rsync failures are Rsync not installed on remote host (which caused failures on Windows, as well as hosts without Rsync installed by default) and remote path not existing, both documented in the linked tickets. However, it is important to note that any error causing Rsync to fail, including SSH transport errors and hostname mismatches, will throw `CommandFailure` for `rsync_to` *and any method that relies on `rsync_to` including `create_remote_file_on()`*. If the remote file is not created for any reason, the command will fail. beaker-4.30.0/docs/how_to/use_hocon_helpers.md000066400000000000000000000034331407603575700213250ustar00rootroot00000000000000# How to Use Hocon Helpers 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). ## hocon_file_read_on 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. ## hocon_file_edit_in_place_on 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. 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. ## hocon_file_edit_on 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. 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. beaker-4.30.0/docs/how_to/use_user_password_authentication.md000066400000000000000000000036731407603575700245020ustar00rootroot00000000000000By 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). ## Example 1: Use 'password' authentication ```yaml HOSTS: pe-centos6: roles: - master - agent - dashboard - database - myrole platform: el-6-i386 snapshot: clean-w-keys hypervisor: fusion ssh: password: anode user: anode auth_methods: - password ``` The log will then read as: _snip_ ``` pe-centos6 20:19:16$ echo hello! 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"} ``` _/snip_ ## Example 2: Use a list of authentication methods 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`. ```yaml HOSTS: pe-centos6: roles: - master - agent - dashboard - database - myrole platform: el-6-i386 snapshot: clean-w-keys hypervisor: fusion CONFIG: ssh: auth_methods: - password - publickey number_of_password_prompts: 0 password: wootwoot ``` beaker-4.30.0/docs/how_to/write_a_beaker_test_for_a_module.md000066400000000000000000000007301407603575700243330ustar00rootroot00000000000000# Beaker for Modules ## Read the Beaker Docs - [Beaker How To](../tutorials/how_to_beaker.md) - [Beaker DSL API](http://rubydoc.info/github/puppetlabs/beaker/frames) ## Understand the Difference Between beaker and beaker-rspec [beaker vs. beaker-rspec](../concepts/beaker_vs_beaker_rspec.md) ## beaker-rspec Details 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. beaker-4.30.0/docs/tutorials/000077500000000000000000000000001407603575700160235ustar00rootroot00000000000000beaker-4.30.0/docs/tutorials/README.md000066400000000000000000000052441407603575700173070ustar00rootroot00000000000000# Tutorials 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. 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! ## Installation If you haven't installed beaker yet, your guide to doing so can be found [here](installation.md). ## Quick Start 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. ## OK, We're Running. Now What? 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. ### Test Writing 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. Checkout our [let's write a test](lets_write_a_test.md) to start with test writing! ### Running Beaker Itself 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. If you want to find out more about running beaker itself, checkout [the command line](the_command_line.md). ### Environment Details 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. Our [creating a test environment doc](creating_a_test_environment.md) is the next spot for you! ### High Level Execution Details 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. 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).beaker-4.30.0/docs/tutorials/creating_a_test_environment.md000066400000000000000000000066301407603575700241310ustar00rootroot00000000000000Hosts/Nodes/SUTs are defined in the --hosts (--config) file in Yaml format. This file defines each node in the test configuration. The file can be saved anywhere and used with `beaker --hosts yourhost.yaml` (see [The Command Line](the_command_line.md) for more info). Example hosts file: ```yaml HOSTS: ubuntu-1404-x64-master: roles: - master - agent - dashboard - database platform: ubuntu-1404-x86_64 hypervisor: vagrant box: puppetlabs/ubuntu-14.04-64-nocm box_url: https://vagrantcloud.com/puppetlabs/boxes/ubuntu-14.04-64-nocm ip: 192.168.20.20 ubuntu-1404-x64-agent: roles: - agent platform: ubuntu-1404-x86_64 hypervisor: vagrant box: puppetlabs/ubuntu-14.04-64-nocm box_url: https://vagrantcloud.com/puppetlabs/boxes/ubuntu-14.04-64-nocm ip: 192.168.21.21 CONFIG: nfs_server: none consoleport: 443 ``` ## Host Requirements Hosts, or SUTs (Systems Under Test), must meet the following requirements: * The SUT will need a properly configured network, hosts will need to be able to reach each other by hostname. * On the SUT, you must configure passwordless SSH authentication for the root user. * The SUT must have the `ntpdate` binary installed. * The SUT must have the `curl` binary installed. * On Windows, `Cygwin` must be installed (with `curl`, `sshd`, `bash`) and the necessary windows gems (`sys-admin`, `win32-dir`, etc). * FOSS install: you must have `git`, `ruby`, and `rdoc` installed on your SUT. ## Required Host Settings To properly define a host you must provide: * name: The string identifying this host. * platform: One of the Beaker supported platforms. ## Optional Host Settings Additionally, Beaker supports the following host options: * ip: The IP address of the SUT. * hypervisor: One of `docker`, `solaris`, `ec2`, `vsphere`, `fusion`, `aix`, `vcloud` or `vagrant`. * Additional settings may be required depending on the selected hypervisor (ie, template, box, box_url, etc). Check the documentation below for your hypervisor for details. * snapshot: The name of the snapshot to revert to before testing. * roles: The 'job' of this host, an array of `master`, `agent`, `frictionless`, `dashboard`, `database`, `default` or any user-defined string. * pe_dir: The directory where PE builds are located, may be local directory or a URL. * pe_ver: The version number of PE to install. * vagrant_memsize: The memory size (in MB) for this host ## Supported Platforms Beaker depends upon each host in the configuration file having a platform type that is correctly formatted and supported. The platform is used to determine how various operations are carried out internally (such as installing packages using the correct package manager for the given operating system). The platform's format is `/^OSFAMILY-VERSION-ARCH.*$/` where `OSFAMILY` is one of: * fedora * debian * oracle * scientific * sles * opensuse * ubuntu * windows * solaris * aix * el (covers centos, redhat and enterprise linux) `VERSION`'s format is not enforced, but should reflect the `OSFAMILY` selected (ie, ubuntu-1204-i386-master, scientific-6-i386-agent, etc). `ARCH`'s format is also not enforced, but should be appropriate to the `OSFAMILY` selected (ie, ubuntu-1204-i386-master, sles-11-x86_64-master, debian-7-amd64-master, etc). ## [Supported Virtualization Providers](../how_to/hypervisors/README.md#external-hypervisors) beaker-4.30.0/docs/tutorials/how_to_beaker.md000066400000000000000000000005171407603575700211600ustar00rootroot00000000000000* [Beaker Installation](installation.md) * [Creating A Test Environment](creating_a_test_environment.md) * [The Command Line](the_command_line.md) * [The Beaker DSL](../how_to/the_beaker_dsl.md) * [Let's Write a Test!](lets_write_a_test.md) * [Access The Live Test Console with Pry](../how_to/access_the_live_test_console_with_pry.md) beaker-4.30.0/docs/tutorials/installation.md000066400000000000000000000106331407603575700210510ustar00rootroot00000000000000# Beaker Installation In most cases, beaker is running on a system separate from the SUT; we will commonly refer to this system as the beaker coordinator. This page outlines how to install requirements for the beaker coordinator and options for the installation of beaker itself. ## Beaker Requirements * Ruby >= 2.1.8 (but we [only test on >= 2.2.5](installation.md#ruby-version)) * libxml2, libxslt (needed for the [Nokogiri](http://nokogiri.org/tutorials/installing_nokogiri.html) gem) * g++ (needed for the [unf_ext](http://rubydoc.info/gems/unf_ext/) gem) * curl (needed for some DSL functions to be able to execute successfully) On a Debian or Ubuntu system you can install these using the command ```console $ sudo apt-get install ruby-dev libxml2-dev libxslt1-dev g++ zlib1g-dev ``` On an EL or Fedora system use: ```console $ sudo yum install make gcc gcc-c++ libxml2-devel libxslt-devel ruby-devel ``` ## Installing Beaker These instructions apply to installing Beaker for use; to set up a development environment for Beaker itself, [see below](#for-development). ### From Gem (Preferred) ```console $ gem install beaker $ beaker --help ``` ### From Latest Git If you need the latest and greatest (and mostly likely broken/untested/no warranty) beaker code. * Uses bundler ```console $ git clone https://github.com/puppetlabs/beaker $ cd beaker $ bundle install $ bundle exec beaker --help ``` ### From Latest Git, As Installed Gem If you need the latest and greatest, but prefer to work from gem instead of through bundler. ```console $ gem uninstall beaker $ git clone https://github.com/puppetlabs/beaker $ cd beaker $ gem build beaker.gemspec $ gem install ./beaker-*.gem ``` ### Special Case Installation The beaker gem can be built and installed in the context of the current test suite by adding the github repos as the source in the Gemspec file (see bundler git documentation). ```ruby source 'https://rubygems.org' group :testing do gem 'cucumber', '~> 1.3.6' gem 'site_prism' gem 'selenium-webdriver' gem 'chromedriver2-helper' gem 'beaker', :github => 'puppetlabs/beaker', :branch => 'master', :ref => 'fffe7' end ``` ## For Development If you intend to make changes to Beaker itself, follow these instructions. ### Recommended Tools It may be necessary to test Beaker against multiple versions of Ruby. The maintainers use [`rbenv`](https://github.com/rbenv/rbenv) to manage multiple Ruby versions; you can install it with Homebrew. You'll also want [`rbenv-bundler`](https://github.com/carsomyr/rbenv-bundler) to keep Gem dependencies from conflicting. ### Setup for Development While most users will use Beaker as a Gem installed from some repository, you will need a live repo to work with. Here's how to configure Beaker and its dependencies so you can start contributing: * Clone your fork of Beaker * Install Beaker's dependencies into `vendor/bundle`: ```console $ cd beaker/ $ bundle install --path vendor/bundle ``` * Installing the dependencies globally ~~may~~ *will probably* cause conflicts and is not recommended. * Please use `vendor/bundle`, not `_vendor` or `.vendor`. * Test your new environment by seeing if the spec tests pass (beaker/master is maintained in an always-passing state): ```console $ rake test:spec # assuming you have rbenv-bundler # or $ bundle exec rake test:spec # if you're *sure* your dependencies are tidy ``` ### Contributing Contributions to Beaker are welcomed, see [CONTRIBUTING.md](/CONTRIBUTING.md)) for instructions. ## Ruby Version In moving to beaker 3.0, we added in a hard requirement that a beaker test writer be using Ruby 2.2.5 or higher. Since Puppet has versions that support earlier versions of Ruby, this made writing tests more difficult than it needed to be. In order to make this easier, in beaker 3.13.0 we've relaxed this requirement to Ruby 2.1.8. Note that the beaker team does not internally test Ruby versions below 2.2.5, and that if bugs are submitted that are found to be specific to versions below 2.2.5, they will not be worked on by the beaker team. This doesn't mean we won't merge fixes to bugs that are specific to those versions that are submitted by the community, however. beaker-4.30.0/docs/tutorials/lets_write_a_test.md000066400000000000000000000066001407603575700220670ustar00rootroot00000000000000## The Task We will write a test to check if a package (specifically HTTPD) is installed and running. To do this we will write two files: 1. `install.rb` - This file will install the package and start the service 2. `mytest.rb` - This file will have our core tests that checks if the package is installed and running Note: We will exclude Windows OS from our testing due to variation of package name. ## Figure out steps What needs to happen in this test: * Install and run HTTPD * Install HTTPD if its not available on our SUT * Start HTTPD service * Testing * Test HTTPD is installed * Test HTTPD service is running ## Create a host configuration file ```console $ beaker-hostgenerator redhat7-64 > redhat7-64.yaml ``` This command will generate a host file for our system under test (SUT). It will use vmpooler as hypervisor for the host. Please check out [this](https://github.com/puppetlabs/beaker/tree/master/docs/how_to/hypervisors) doc to learn more about hypervisors for beaker. ## Install and run HTTPD Make a file named `install.rb` and put the following code into it: ```ruby test_name "Installing and runnning HTTPD" do # Don't run the install script on the following platform confine :except, :platform => 'windows' step "Install HTTPD" do hosts.each do |host| # Install HTTPD if it is not available on our SUT install_package(host, 'httpd') unless check_for_package(host, 'httpd') end end step "Start HTTPD" do hosts.each do |host| # Start HTTPD service on(host, "service httpd start") end end end ``` This places our install steps in a ruby script (`install.rb`) which will run on your SUT. The install.rb script is used in our commandline to beaker, below. ## Create a test file Lets create test file that tests if HTTPD is installed and running on our hosts. Make a file called `mytest.rb` and add the following code to it: ```ruby test_name "Check if HTTPD is installed and running" do # Don't run these tests on the following platform confine :except, :platform => 'windows' step "Make sure HTTPD is installed" do hosts.each do |host| # Check if HTTPD is installed assert check_for_package(host, 'httpd') end end step "Make sure HTTPD is running" do hosts.each do |host| on(host, "systemctl is-active httpd") do |result| # Check if HTTPD is running assert_equal(0, result.exit_code) end end end end ``` ## Run it! You can now run this with ```console $ beaker --host redhat7-64ma.yaml --pre-suite install.rb --tests mytest.rb ``` ## Creating the host configuration on at runtime When you don't want to store a static file, you can also let [beaker-hostgenerator](https://github.com/puppetlabs/beaker-hostgenerator) generate it on the fly. The filename is used as a host specification. A simple example is: ```console $ beaker --host centos7-64 --pre-suite install.rb --tests mytest.rb ``` It's also possible to set the hypervisor and hostname: ```console $ beaker --host 'centos7-64{hypervisor=docker,hostname=centos7-64.example.com}' --pre-suite install.rb --tests mytest.rb ``` An alternative way to set the hypervisor is `BEAKER_HYPERVISOR`: ```console $ BEAKER_HYPERVISOR=docker beaker --host centos7-64 --pre-suite install.rb --tests mytest.rb ``` Next up you may want to look at the [Beaker test for a module](../how_to/write_a_beaker_test_for_a_module.md) page. beaker-4.30.0/docs/tutorials/quick_start_rake_tasks.md000066400000000000000000000152071407603575700231120ustar00rootroot00000000000000# Beaker Quick Start Tasks We have developed some rake tasks to help new Beaker users get up and running quickly with writing and running tests. ## Pre-requisites * You will need to have already completed the Beaker installation tutorial - [Beaker Installation](installation.md) * Hypervisors are services that provision SUTs for Beaker. We have made two available in this quick start guide to allow you to get up and running. See the docs on how to setup [Vmpooler](https://github.com/puppetlabs/beaker-vmpooler/blob/master/vmpooler.md) and [Vagrant](https://github.com/puppetlabs/beaker-vagrant/blob/master/docs/vagrant.md). ## How to use them To use the tasks, you need to put the following line at the top of your project's rake file: ```rake require 'beaker/tasks/quick_start' ``` To check that you have access to the quickstart tasks from your project, run: ```console rake --tasks ``` You should see them listed along with any rake tasks you have defined in your local project rakefile: ```rake rake beaker_quickstart:gen_hosts[hypervisor] # Generate Default Beaker Host Config File, valid options are: vmpooler or vagrant rake beaker_quickstart:gen_pre_suite # Generate Default Pre-Suite rake beaker_quickstart:gen_smoke_test # Generate Default Smoke Test rake beaker_quickstart:run_test[hypervisor] # Run Default Smoke Test, after generating default host config and test files, valid options are: vmpooler or vagrant ``` ## Tasks ### Hypervisor Argument Some of the tasks below take a 'hypervisor' argument that you can pass either 'vmpooler' or 'vagrant' to. If you leave it empty, the task will default to using 'vagrant'. Example: ```console $ rake beaker_quickstart:gen_hosts[vmpooler] ``` If you have the zsh shell then you will need to escape the square brackets: ```console rake beaker_quickstart:gen_hosts\[vmpooler\] ``` ### Generate tasks These tasks are standalone and can be run independently from each other to generate the desired files. * beaker_quickstart:gen_hosts (generates default host config) * beaker_quickstart:gen_pre_suite (generates default pre-suite) * beaker_quickstart:gen_smoke_test (generates default smoke test) #### gen_hosts To run: ```console $ rake beaker_quickstart:gen_hosts[hypervisor] ``` The gen_hosts task will create a file 'default_hypervisor_hosts.yaml' in acceptance/config. If the file already exists, it will not be overwritten. This will allow you to play around with the config yourself, either by manually editing the file or by using beaker-hostgenerator to generate a new hosts config. Vmpooler file (redhat 7 master and agent): ```yaml --- HOSTS: redhat7-64-1: pe_dir: pe_ver: pe_upgrade_dir: pe_upgrade_ver: hypervisor: vmpooler platform: el-7-x86_64 template: redhat-7-x86_64 roles: - agent - master - database - dashboard - classifier - default redhat7-64-2: pe_dir: pe_ver: pe_upgrade_dir: pe_upgrade_ver: hypervisor: vmpooler platform: el-7-x86_64 template: redhat-7-x86_64 roles: - agent - frictionless CONFIG: nfs_server: none consoleport: 443 pooling_api: http://vmpooler.delivery.puppetlabs.net/ ``` Vagrant file (ubuntu 14 master and agent): ```yaml --- HOSTS: ubuntu1404-64-1: pe_dir: pe_ver: pe_upgrade_dir: pe_upgrade_ver: platform: ubuntu-14.04-amd64 hypervisor: vagrant roles: - agent - master - database - dashboard - classifier - default ubuntu1404-64-2: pe_dir: pe_ver: pe_upgrade_dir: pe_upgrade_ver: platform: ubuntu-14.04-amd64 hypervisor: vagrant roles: - agent - frictionless CONFIG: nfs_server: none consoleport: 443 box_url: https://vagrantcloud.com/puppetlabs/boxes/ubuntu-14.04-64-nocm box: puppetlabs/ubuntu-14.04-64-nocm ``` For more info on host generation and what these configs represent see - [Creating A Test Environment](creating_a_test_environment.md) ### `gen_pre_suite` To run: ```console $ rake beaker_quickstart:gen_pre_suite ``` This task will generate a default Beaker pre-suite file. The gen_pre-suite task will create a file 'default_pre_suite.rb' in acceptance/setup. If the file already exists, it will not be overwritten. This will allow you to edit the pre_suite file yourself if required. pre_suite file: ```ruby install_puppet ``` See the [Test Suites doc](test_suites.md) in this directory for more information on pre-suites. ### `gen_smoke_test` To run: ```console rake beaker_quickstart:gen_smoke_test ``` This task will generate a default Beaker smoke test file. The gen_smoke_test task will create a file 'default_smoke_test.rb' in acceptance/tests. If the file already exists, it will not be overwritten. This will allow you to edit the test file yourself if required. smoke test file: ```ruby test_name 'puppet install smoketest' do step 'puppet install smoketest: verify \'puppet help\' can be successfully called on all hosts' do hosts.each do |host| on host, puppet('help') end end end ``` This smoke test will check that Puppet has been successfully installed on the hosts. For more information on the Beaker dsl methods available to you in your tests see - [Beaker dsl](../how_to/the_beaker_dsl.md) ### Run task The beaker_quickstart:run_test task will run all the above tasks in sequential order, to generate a hosts file, pre-suite file, smoke test and then use these files to perform a Beaker test run. If the files already exist (see below for further info on file names and location) then they will not be overwritten. #### run_test To run: ```console $ rake beaker_quickstart:run_test[hypervisor] ``` This task will run the above 3 tasks in sequential order and then execute a Beaker test run using all 3 files. ```console $ beaker --hosts acceptance/config/default_vmpooler_hosts.yaml --pre-suite acceptance/setup/default_pre_suite.rb --tests acceptance/tests/default_smoke_test.rb ``` You will end up with provisioned hosts with puppet installed and a test check executed to verify that puppet was installed. For more information on running Beaker tests see - [Test run](test_run.md) beaker-4.30.0/docs/tutorials/subcommands.md000066400000000000000000000045371407603575700206710ustar00rootroot00000000000000# Using Subcommands The document gives an overview of the subcommands that Beaker supports and describes how to use them. ## Why Subcommands? 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. 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`. Please note that in this document, a Beaker `run` is standard beaker invocation without any subcommands. ## Available Subcommands ### beaker init 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. ### beaker provision Provisions hosts defined in your `subcommand_options file`. You can pass the `--hosts` flag here to override any hosts provided there. ### beaker exec 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` ### beaker destroy Execute this command to deprovision your systems under test(SUTs). ## Basic workflow ```console $ beaker init -h hosts_file -o options_file --keyfile ssh_key --pre-suite ./setup/pre-suit/my_presuite.rb $ beaker provision # Note: do not pass in hosts file, or use the '-t' flag! Just the file # or directory. Do not pass GO. Do not collect $200. $ beaker exec ./tests/my_test.rb # Repeating the above command as needed # When you're done testing using the VM that Beaker provisioned $ beaker destroy ``` beaker-4.30.0/docs/tutorials/test_run.md000066400000000000000000000015701407603575700202130ustar00rootroot00000000000000# Beaker Test Runs 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. ## Provisioning * Using supported hypervisors, provision SUTs for testing on * Do any initial configuration to ensure that the SUTs can communicate with beaker and each other * skip with `--no-provision` * Provisioning also runs some basic setup on the SUTs * Validation * Check the SUTs for necessary packages (curl, ntpdate) * skip with `--no-validate` * Configuration * Do any post-provisioning configuration to the SUTs * skip with `--no-configure` ## Execution * Execute the files specified in each of the suites; for further documentation, please refer to [Test Suites & Failure Modes](test_suites.md) beaker-4.30.0/docs/tutorials/test_suites.md000066400000000000000000000061031407603575700207200ustar00rootroot00000000000000# Test Suites & Failure Modes Beaker test suites correspond to test suites in most testing frameworks, being containers for tests or pre- or post-testing files to execute. There are two main ways that you specify which test suites a particular file belongs in. The first way is to use the Beaker command line interface (CLI) arguments. These are specified in runtime order below: --pre-suite —-tests —-post-suite —-pre-cleanup If you’d like to find out more information about these arguments, or how exactly you pass the specified files to each suite, execute `beaker —-help` at the CLI. The second way is to provide suite arguments through config files. There are two places that you can provide this info: 1. The `CONFIG` section of a hosts file. 2. A local options file passed through the `--options-file` CLI argument. Either way, the keys for the arguments needed are listed below, respective to the arguments listed above: :pre_suite :tests :post_suite :pre_cleanup *Note* that one difference between the two methods is that if you provide the options via the CLI, we support various input methods such as specifying individual files vs paths to folders. The second option (providing keys to config files) only works if each file is fully specified. Beaker will not find all files in a directory in the second case, that expansion is only done on the CLI inputs. # Suite Details, & Failure Mode Behavior This section is to explain the particulars of any suite, and any warnings or notes about using them, including how they behave in Beaker’s different failure modes. ## Pre-Suite The pre-suite is for setting up the Systems Under Test (SUTs) for the testing suite. No surprises here, usually these files are filled with the setup and installation code needed to verify that the operating assumptions of the software being tested are true. Pre-suites, since they’re supposed to contain just setup code, will fail-fast and the entire Beaker run will be abandoned if setup fails, since testing assumes that setup has succeeded. ## Tests Time to actually test! This suite contains the test files that you would like to verify new code with. The test suite behaves according to the global fail-mode setting. ## Post-Suite Usually the post-suite is used to clean up any fixtures that your tests might have poisoned, collect any log files that you’d like to later review, and to get any resources from the SUTs before they are cleaned up / deleted. The post-suite only runs if the fail-mode is set to slow, which it is by default. Fast fail-mode will skip this suite, so that you can get feedback quicker, and you can potentially check out the system in the state exactly that it failed in. ## Pre-cleanup The pre-cleanup suite is for tasks that you’d like to run at the end of your tests, regardless of Beaker’s fail-mode. You can think of this behaving as a `finally` block at the end of a `try-rescue` one. The pre-cleanup suite falls back to the global fail-mode setting, which defaults to slow, meaning it will run all tests regardless of any early failures. beaker-4.30.0/docs/tutorials/the_command_line.md000066400000000000000000000025431407603575700216360ustar00rootroot00000000000000# The Command Line * Using the gem ```console $ beaker --log-level debug --hosts sample.cfg --tests test.rb ``` * Using latest git ```console $ bundle exec beaker --log-level debug --hosts sample.cfg --tests test.rb ``` ## Useful options * `-h, --hosts FILE `, the hosts that you are going to be testing with * `--log-level debug`, for providing verbose logging and full stacktraces on failure * `--[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 * `--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. * `--parse-only`, read and parse all command line options, environment options and file options; report the parsed options and exit. ## The Rest See all options with * Using the gem ```console $ beaker --help ``` * Using latest git ```console $ bundle exec beaker --help ```beaker-4.30.0/ext/000077500000000000000000000000001407603575700136455ustar00rootroot00000000000000beaker-4.30.0/ext/completion/000077500000000000000000000000001407603575700160165ustar00rootroot00000000000000beaker-4.30.0/ext/completion/beaker-completion.bash000066400000000000000000000040441407603575700222570ustar00rootroot00000000000000## bash completion script for beaker # The contained completion routines provide support for completing: # # *) any option specified in beaker --help # but not yet the arguments to the options # To use these routines: # # 1) Copy this file to somewhere (e.g. ~/.beaker-completion.sh). # 2) Add the following line to your .bashrc/.zshrc: # source ~/.beaker-completion.sh # 3) tab will now complete beaker's command line options _beaker_complete() { # COMP_WORDS is an array of words in the current command line. # COMP_CWORD is the index of the current word (the one the cursor is # in). So COMP_WORDS[COMP_CWORD] is the current word; we also record # the previous word here, although this specific script doesn't # use it yet. local cur_word="${COMP_WORDS[COMP_CWORD]}" local prev_word="${COMP_WORDS[COMP_CWORD-1]}" # Ask beaker to generate a list of args # ensure other warnings/errors on stderr go to null local beaker_help=`beaker --help 2>/dev/null` # parse out commands and switches # grep extended regex, only print match local dash_words=`echo "${beaker_help}" | grep -oE ' \-(\w|\[|\]|-)+' | uniq` # parse for negated commands local negative_words=`echo "${dash_words}" | grep -oE '\-\-\[\w+-\](\w|-)+' | sed 's/\[//' | sed 's/\]//'` # remove the negative portion from dash_words local dash_words=`echo "${dash_words}" | sed 's/\[no\-\]//g'` # TODO: # Parse out arguments to commands # Perform completion if the previous word is doubledash option and current word in argslist, # Perform completion if the current word starts with a dash ('-'), if [[ ${cur_word} == -* ]] ; then # COMPREPLY is the array of possible completions, generated with # the compgen builtin. COMPREPLY=( $(compgen -W "${dash_words} ${negative_words}" -- ${cur_word}) ) else COMPREPLY=() fi return 0 } # Register _beaker_complete to provide completion for the following commands complete -F _beaker_complete -o default beaker pe-beaker beaker-4.30.0/lib/000077500000000000000000000000001407603575700136135ustar00rootroot00000000000000beaker-4.30.0/lib/beaker.rb000066400000000000000000000023331407603575700153720ustar00rootroot00000000000000require 'rubygems' unless defined?(Gem) module Beaker %w( version platform test_suite test_suite_result result command options network_manager cli perf logger_junit subcommand ).each do |lib| begin require "beaker/#{lib}" rescue LoadError require File.expand_path(File.join(File.dirname(__FILE__), 'beaker', lib)) end end # These really are our sub-systems that live within the harness today # Ideally I would like to see them split out into modules that can be # included as such here # # The Testing DSL require 'beaker/dsl' # # Our Host Abstraction Layer require 'beaker/host' # # Our Hypervisor Abstraction Layer require 'beaker/hypervisor' # # How we manage connecting to hosts and hypervisors #require 'beaker/connectivity' # # Our test runner, suite, test cases and steps #require 'beaker/runner' # # Common setup and testing steps #require 'beaker/steps' # InParallel, for executing in parallel require 'in_parallel' # Shared methods and helpers require 'beaker/shared' # MiniTest, for including MiniTest::Assertions require 'minitest/test' # Add pry support when available begin require 'pry' rescue LoadError # do nothing end end beaker-4.30.0/lib/beaker/000077500000000000000000000000001407603575700150445ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/cli.rb000066400000000000000000000305501407603575700161430ustar00rootroot00000000000000module Beaker class CLI VERSION_STRING = " wWWWw |o o| | O | %s! |(\")| / \\X/ \\ | V | | | | " attr_reader :logger, :options, :network_manager def initialize @timestamp = Time.now # Initialize a logger object prior to parsing; this should be overwritten whence # the options are parsed and replaced with a new logger based on what is passed # in to configure the logger. @logger = Beaker::Logger.new @options = {} end def parse_options(args = ARGV) @options_parser = Beaker::Options::Parser.new @options = @options_parser.parse_args(args) @attribution = @options_parser.attribution @logger = Beaker::Logger.new(@options) InParallel::InParallelExecutor.logger = @logger @options_parser.update_option(:logger, @logger, 'runtime') @options_parser.update_option(:timestamp, @timestamp, 'runtime') @options_parser.update_option(:beaker_version, Beaker::Version::STRING, 'runtime') beaker_version_string = VERSION_STRING % @options[:beaker_version] # Some flags should exit early if @options[:help] @logger.notify(@options_parser.usage) exit(0) end if @options[:beaker_version_print] @logger.notify(beaker_version_string) exit(0) end if @options[:parse_only] print_version_and_options exit(0) end #add additional paths to the LOAD_PATH if not @options[:load_path].empty? @options[:load_path].each do |path| $LOAD_PATH << File.expand_path(path) end end @options[:helper].each do |helper| require File.expand_path(helper) end self end # only call this method after parse_options has been executed. def print_version_and_options @logger.info("Beaker!") @logger.info(VERSION_STRING % @options[:beaker_version]) @logger.info(@options.dump) end # Provision, validate and configure all hosts as defined in the hosts file def provision begin @hosts = [] initialize_network_manager @network_manager.proxy_package_manager @network_manager.validate @network_manager.configure rescue => e report_and_raise(@logger, e, "CLI.provision") end self end #Initialize the network manager so it can initialize hosts for testing for subcommands def initialize_network_manager begin @network_manager = Beaker::NetworkManager.new(@options, @logger) @hosts = @network_manager.provision rescue => e report_and_raise(@logger, e, "CLI.initialize_network_manager") end end # Run Beaker tests. # # - run pre-suite # - run tests # - run post-suite # - cleanup hosts def execute! print_version_and_options begin trap(:INT) do @logger.warn "Interrupt received; exiting..." exit(1) end # Setup perf monitoring if needed if @options[:collect_perf_data].to_s =~ /(aggressive)|(normal)/ @perf = Beaker::Perf.new( @hosts, @options ) end errored = false #pre acceptance phase run_suite(:pre_suite, :fast) #testing phase begin run_suite(:tests, @options[:fail_mode]) #post acceptance phase rescue => e #post acceptance on failure #run post-suite if we are in fail-slow mode if @options[:fail_mode].to_s =~ /slow/ run_suite(:post_suite) @perf.print_perf_info if defined? @perf end raise e else #post acceptance on success run_suite(:post_suite) @perf.print_perf_info if defined? @perf end #cleanup phase rescue => e begin run_suite(:pre_cleanup) rescue => e # pre-cleanup failed @logger.error "Failed running the pre-cleanup suite." end #cleanup on error if @options[:preserve_hosts].to_s =~ /(never)|(onpass)/ @logger.notify "Cleanup: cleaning up after failed run" if @network_manager @network_manager.cleanup end else preserve_hosts_file end print_reproduction_info( :error ) @logger.error "Failed running the test suite." puts '' exit 1 else begin run_suite(:pre_cleanup) rescue => e # pre-cleanup failed @logger.error "Failed running the pre-cleanup suite." end #cleanup on success if @options[:preserve_hosts].to_s =~ /(never)|(onfail)/ @logger.notify "Cleanup: cleaning up after successful run" if @network_manager @network_manager.cleanup end else preserve_hosts_file end if @logger.is_debug? print_reproduction_info( :debug ) end end end #Run the provided test suite #@param [Symbol] suite_name The test suite to execute #@param [String] failure_strategy How to proceed after a test failure, 'fast' = stop running tests immediately, 'slow' = # continue to execute tests. def run_suite(suite_name, failure_strategy = nil) if (@options[suite_name].empty?) @logger.notify("No tests to run for suite '#{suite_name.to_s}'") return end Beaker::TestSuite.new( suite_name, @hosts, @options, @timestamp, failure_strategy ).run_and_raise_on_failure end # Get the list of options that are not equal to presets. # @return Beaker::Options::OptionsHash def configured_options result = Beaker::Options::OptionsHash.new @attribution.each do |attribute, setter| if setter != 'preset' result[attribute] = @options[attribute] end end result end # Sets aside the current hosts file for re-use with the --no-provision flag. # This is originally intended for use on a successful tests where the hosts # are preserved (the --preserve-hosts option is set accordingly). # It copies the current hosts file to the log directory, and rewrites the SUT # names to match their names during the finishing run. # # @return nil def preserve_hosts_file # things that don't belong in the preserved host file dontpreserve = /HOSTS|logger|timestamp|log_prefix|_dated_dir|logger_sut/ # set the pre/post/tests to be none @options[:pre_suite] = [] @options[:post_suite] = [] @options[:tests] = [] @options[:pre_cleanup] = [] preserved_hosts_filename = File.join(@options[:log_dated_dir], 'hosts_preserved.yml') hosts_yaml = @options hosts_yaml['HOSTS'] = combined_instance_and_options_hosts hosts_yaml['CONFIG'] = Beaker::Options::OptionsHash.new.merge(hosts_yaml['CONFIG'] || {}) # save the rest of the options, excepting the HOSTS that we have already processed hosts_yaml['CONFIG'] = hosts_yaml['CONFIG'].merge(@options.reject{ |k,v| k =~ dontpreserve }) # remove copy of HOSTS information hosts_yaml['CONFIG']['provision'] = false File.open(preserved_hosts_filename, 'w') do |file| YAML.dump(hosts_yaml, file) end @options[:hosts_preserved_yaml_file] = preserved_hosts_filename end # Return a host_hash that is a merging of options host hashes with instance host objects # @return Hash def combined_instance_and_options_hosts hosts_yaml = @options newly_keyed_hosts_entries = {} hosts_yaml['HOSTS'].each do |host_name, file_host_hash| h = Beaker::Options::OptionsHash.new file_host_hash = h.merge(file_host_hash) @hosts.each do |host| if host_name.to_s == host.name.to_s newly_keyed_hosts_entries[host.hostname] = file_host_hash.merge(host.host_hash) break end end end newly_keyed_hosts_entries end # Prints all information required to reproduce the current run & results to the log # @see #print_env_vars_affecting_beaker # @see #print_command_line # # @return nil def print_reproduction_info( log_level = :debug ) print_command_line( log_level ) print_env_vars_affecting_beaker( log_level ) end # Prints Environment variables affecting the beaker run (those that # beaker introspects + the ruby env that beaker runs within) # @param [Symbol] log_level The log level (coloring) to print the message at # @example Print pertinent env vars using error leve reporting (red) # print_env_vars_affecting_beaker :error # # @return nil def print_env_vars_affecting_beaker( log_level ) non_beaker_env_vars = [ 'BUNDLE_PATH', 'BUNDLE_BIN', 'GEM_HOME', 'GEM_PATH', 'RUBYLIB', 'PATH'] env_var_map = non_beaker_env_vars.inject({}) do |memo, possibly_set_vars| set_var = Array(possibly_set_vars).detect {|possible_var| ENV[possible_var] } memo[set_var] = ENV[set_var] if set_var memo end env_var_map = env_var_map.merge(Beaker::Options::Presets.new.env_vars) @logger.send( log_level, "\nImportant ENV variables that may have affected your run:" ) env_var_map.each_pair do |var, value| if value.is_a?(Hash) value.each_pair do | subvar, subvalue | @logger.send( log_level, " #{subvar}\t\t#{subvalue}" ) end else @logger.send( log_level, " #{var}\t\t#{value}" ) end end end # Prints the command line that can be called to reproduce this run # (assuming the environment is the same) # @param [Symbol] log_level The log level (coloring) to print the message at # @example Print pertinent env vars using error level reporting (red) # print_command_line :error # # @note Re-use of already provisioned SUTs has been tested against the vmpooler & vagrant boxes. # Fusion doesn't need this, as it has no cleanup steps. Docker is untested at this time. # Please contact @electrical or the Puppet QE Team for more info, or for requests to support this. # # @return nil def print_command_line( log_level = :debug ) @logger.send(log_level, "\nYou can reproduce this run with:\n") @logger.send(log_level, @options[:command_line]) if @options[:hosts_preserved_yaml_file] set_docker_warning = false has_supported_hypervisor = false @hosts.each do |host| case host[:hypervisor] when /vagrant|fusion|vmpooler|vcloud/ has_supported_hypervisor = true when /docker/ set_docker_warning = true end end if has_supported_hypervisor reproducing_command = build_hosts_preserved_reproducing_command(@options[:command_line], @options[:hosts_preserved_yaml_file]) @logger.send(log_level, "\nYou can re-run commands against the already provisioned SUT(s) with:\n") @logger.send(log_level, '(docker support is untested for this feature. please reference the docs for more info)') if set_docker_warning @logger.send(log_level, reproducing_command) end end end # provides a new version of the command given, edited for re-use with a # preserved host. It does this by swapping the hosts file out for the # new_hostsfile argument and removing any previously set provisioning # flags that it finds # (we add +:provision => false+ in the new_hostsfile itself). # # @param [String] command Command line parameters to edit. # @param [String] new_hostsfile Path to the new hosts file to use. # # @return [String] The command line parameters edited for re-use def build_hosts_preserved_reproducing_command(command, new_hostsfile) command_parts = command.split(' ') replace_hosts_file_next = false reproducing_command = [] command_parts.each do |part| if replace_hosts_file_next reproducing_command << new_hostsfile replace_hosts_file_next = false next elsif part == '--provision' || part == '--no-provision' next # skip any provisioning flag. This is handled in the new_hostsfile itself elsif part == '--hosts' replace_hosts_file_next = true end reproducing_command << part end reproducing_command.join(' ') end end end beaker-4.30.0/lib/beaker/command.rb000066400000000000000000000152261407603575700170150ustar00rootroot00000000000000module Beaker # An object that represents a "command" on a remote host. Is responsible # for munging the environment correctly. Probably poorly named. # # @api public class Command # A string representing the (possibly) incomplete command attr_accessor :command # A hash key-values where the keys are environment variables to be set attr_accessor :environment # A hash of options. Keys with values of nil are considered flags attr_accessor :options # An array of additional arguments to be supplied to the command attr_accessor :args # @param [String] command The program to call, either an absolute path # or one in the PATH (can be overridden) # @param [Array] args These are addition arguments to the command # @param [Hash] options These are addition options to the command. They # will be added in "--key=value" after the command # but before the arguments. There is a special key, # 'ENV', that won't be used as a command option, # but instead can be used to set any default # environment variables # # @example Recommended usage programmatically: # Command.new('git add', files, :patch => true, 'ENV' => {'PATH' => '/opt/csw/bin'}) # # @example My favorite example of a signature that we must maintain # Command.new('puppet', :resource, 'scheduled_task', name, # [ 'ensure=present', # 'command=c:\\\\windows\\\\system32\\\\notepad2.exe', # "arguments=args-#{name}" ] ) # # @note For backwards compatability we must support any number of strings # or symbols (or arrays of strings an symbols) and essentially # ensure they are in a flattened array, coerced to strings, and # call #join(' ') on it. We have options for the command line # invocation that must be turned into '--key=value' and similarly # joined as well as a hash of environment key value pairs, and # finally we need a hash of options to control the default envs that # are included. def initialize command, args = [], options = {} @command = command @options = options @args = args @environment = {} @cmdexe = @options.delete(:cmdexe) || false @prepend_cmds = @options.delete(:prepend_cmds) || nil @append_cmds = @options.delete(:append_cmds) || nil # this is deprecated and will not allow you to use a command line # option of `--environment`, please use ENV instead. [:ENV, :environment, 'environment', 'ENV'].each do |k| if @options[k].is_a?(Hash) @environment = @environment.merge(@options.delete(k)) end end end # @param [Host] host An object that implements {Beaker::Host}'s # interface. # @param [String] cmd An command to call. # @param [Hash] env An optional hash of environment variables to be used # @param [String] pc An optional list of commands to prepend # # @return [String] This returns the fully formed command line invocation. def cmd_line host, cmd = @command, env = @environment, pc = @prepend_cmds, ac = @append_cmds env_string = host.environment_string( env ) prepend_commands = host.prepend_commands( cmd, pc, :cmd_exe => @cmdexe ) append_commands = host.append_commands( cmd, ac, :cmd_exe => @cmdexe ) # This will cause things like `puppet -t -v agent` which is maybe bad. if host[:platform] =~ /cisco_ios_xr/ cmd_line_array = [prepend_commands, env_string, cmd, options_string, args_string, append_commands] else cmd_line_array = [env_string, prepend_commands, cmd, options_string, args_string, append_commands] end cmd_line_array.compact.reject( &:empty? ).join( ' ' ) end # @param [Hash] opts These are the options that the command takes # # @return [String] String of the options and flags for command. # # @note Why no. Not the least bit Unixy, why do you ask? def options_string opts = @options flags = [] options = opts.dup options.each_key do |key| if options[key] == nil flags << key options.delete(key) end end short_flags, long_flags = flags.partition {|flag| flag.to_s.length == 1 } parsed_short_flags = short_flags.map {|f| "-#{f}" } parsed_long_flags = long_flags.map {|f| "--#{f}" } short_opts, long_opts = {}, {} options.each_key do |key| if key.to_s.length == 1 short_opts[key] = options[key] else long_opts[key] = options[key] end end parsed_short_opts = short_opts.map {|k,v| "-#{k}=#{v}" } parsed_long_opts = long_opts.map {|k,v| "--#{k}=#{v}" } return (parsed_short_flags + parsed_long_flags + parsed_short_opts + parsed_long_opts).join(' ') end # @param [Array] args An array of arguments to the command. # # @return [String] String of the arguments for command. def args_string args = @args args.flatten.compact.join(' ') end end class PuppetCommand < Command def initialize *args command = "puppet #{args.shift}" opts = args.last.is_a?(Hash) ? args.pop : Hash.new opts['ENV'] ||= Hash.new opts[:cmdexe] = true super( command, args, opts ) end end class HostCommand < Command def cmd_line host eval "\"#{@command}\"" end end class SedCommand < Command # sets up a SedCommand for a particular platform # # the purpose is to abstract away platform-dependent details of the sed command # # @param [String] platform The host platform string # @param [String] expression The sed expression # @param [String] filename The file to apply the sed expression to # @param [Hash{Symbol=>String}] opts Additional options # @option opts [String] :temp_file The temp file to use for in-place substitution # (only applies to solaris hosts, they don't provide the -i option) # # @return a new {SedCommand} object def initialize platform, expression, filename, opts = {} command = "sed -i -e \"#{expression}\" #{filename}" if platform =~ /solaris|aix|osx|openbsd/ command.slice! '-i ' temp_file = opts[:temp_file] ? opts[:temp_file] : "#{filename}.tmp" command << " > #{temp_file} && mv #{temp_file} #{filename} && rm -f #{temp_file}" end args = [] opts['ENV'] ||= Hash.new super( command, args, opts ) end end end beaker-4.30.0/lib/beaker/command_factory.rb000066400000000000000000000031311407603575700205340ustar00rootroot00000000000000require 'minitest/test' module Beaker module CommandFactory include Minitest::Assertions #Why do we need this accessor? # https://github.com/seattlerb/minitest/blob/master/lib/minitest/assertions.rb#L8-L12 # Protocol: Nearly everything here boils up to +assert+, which # expects to be able to increment an instance accessor named # +assertions+. This is not provided by Assertions and must be # provided by the thing including Assertions. See Minitest::Runnable # for an example. attr_accessor :assertions def assertions @assertions || 0 end # Helper to create & run commands # # @note {Beaker::Host#exec} gets passed a duplicate of the options hash argument. # @note {Beaker::Command#initialize} gets passed selected options from the # options hash argument. Specifically, :prepend_cmds & :cmdexe. # # @param [String] command Command to run # @param [Hash{Symbol=>Boolean, Array}] options Options to pass # through for command execution # # @api private # @return [String] Stdout from command execution def execute(command, options={}, &block) cmd_create_options = {} exec_opts = options.dup cmd_create_options[:prepend_cmds] = exec_opts.delete(:prepend_cmds) || nil cmd_create_options[:cmdexe] = exec_opts.delete(:cmdexe) || false result = self.exec(Command.new(command, [], cmd_create_options), exec_opts) if block_given? yield result else result.stdout.chomp end end def fail_test(msg) assert(false, msg) end end end beaker-4.30.0/lib/beaker/dsl.rb000066400000000000000000000063521407603575700161610ustar00rootroot00000000000000[ 'install_utils', 'roles', 'outcomes', 'assertions', 'patterns', 'structure', 'helpers', 'wrappers', 'test_tagging' ].each do |lib| require "beaker/dsl/#{lib}" end module Beaker # This is a catch all module for including Puppetlabs home grown testing # DSL. This module is mixed into {Beaker::TestCase} and can be # mixed into any test runner by defining the methods that it requires to # interact with. If not all of the functionality is required sub modules of # the DSL may be mixed into a test runner of your choice. # # Currently most DSL modules require #logger and #hosts defined. #logger # should provided the methods #debug, #warn and #notify and may be a # wrapper to any logger you wish (or {Beaker::Logger}). #hosts # should return an array of objects which conform to the interface defined # in {Beaker::Host} (primarily it should provide Hash like access # and interfaces like {Beaker::Host#exec}, # {Beaker::Host#do_scp_to}, and # {Beaker::Host#do_scp_from}. # # # @example Writing a complete testcase to be ran by the builtin test runner. # test_name 'Ensure My App Starts Correctly' do # confine :except, :platform => ['windows', 'solaris'] # # teardown do # on master, puppet('resource mything ensure=absent') # on agents, 'kill -9 allTheThings' # end # # step 'Ensure Pre-Requisites are Installed' do # end # # with_puppet_running_on master, :master, :logdest => '/tmp/blah' do # # step 'Run Startup Script' do # end # # step 'And... Did it work?' do # end # end # end # # @example Writing an Example to be ran within RSpec # #=> spec_helper.rb # RSpec.configure do |c| # c.include 'beaker/dsl/helpers' # c.include 'beaker/dsl/rspec/matchers' # c.include 'beaker/dsl/rspec/expectations' # c.include 'beaker/host' # end # # #=> my_acceptance_spec.rb # require 'spec_helper' # # describe 'A Test With RSpec' do # let(:hosts) { Host.new('blah', 'blah', 'not helpful') } # let(:logger) { Where.is('the', 'rspec', 'logger') } # # after do # on master, puppet('resource mything ensure=absent') # on agents, 'kill -9 allTheThings' # end # # it 'tests stuff?' do # result = on( hosts.first, 'ls ~' ) # expect( result.stdout ).to match /my_file/ # end # end # #@api dsl module DSL include Beaker::DSL::Roles include Beaker::DSL::Outcomes include Beaker::DSL::Structure include Beaker::DSL::Assertions include Beaker::DSL::Wrappers include Beaker::DSL::Helpers include Beaker::DSL::InstallUtils include Beaker::DSL::Patterns include Beaker::DSL::TestTagging def self.register(helper_module) include helper_module # Modules added into a module which has previously been included are not # retroactively included in the including class. Do this here so we don't # have to in every DSL extension library. # # https://github.com/adrianomitre/retroactive_module_inclusion Beaker::TestCase.class_eval { include Beaker::DSL } end end end beaker-4.30.0/lib/beaker/dsl/000077500000000000000000000000001407603575700156265ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/dsl/assertions.rb000066400000000000000000000110411407603575700203420ustar00rootroot00000000000000require 'minitest/test' module Beaker module DSL # Any custom assertions for Test::Unit or minitest live here. You may # include them in your own testing if you wish, override them, or re-open # the class to register new ones for use within # {Beaker::TestCase}. # # You may use any test/unit assertion within your assertion. The # assertion below assumes access to the method #result which will # contain the output (according to the interface defined in # {Beaker::Result}). When writing your own, to make them more # portable and less brittle it is recommended that you pass the result # or direct object for asserting against into your assertion. # module Assertions include Minitest::Assertions #Why do we need this accessor? # https://github.com/seattlerb/minitest/blob/master/lib/minitest/assertions.rb#L8-L12 # Protocol: Nearly everything here boils up to +assert+, which # expects to be able to increment an instance accessor named # +assertions+. This is not provided by Assertions and must be # provided by the thing including Assertions. See Minitest::Runnable # for an example. attr_accessor :assertions def assertions @assertions || 0 end # Make assertions about the content of console output. # # By default, each line of +output+ is assumed to come from STDOUT. # You may specify the stream explicitly by annotating the line with a # stream marker. (If your line literally requires any stream marker at # the beginning of a line, you must prefix the line with an explicit # stream marker.) The currently recognized markers are: # # * "STDOUT> " # * "STDERR> " # * "OUT> " # * "ERR> " # * "1> " # * "2> " # # Any leading common indentation is automatically removed from the # +output+ parameter. For cases where this matters (e.g. every line # should be indented), you should prefix the line with an explicit # stream marker. # # @example Assert order of interleaved output streams # !!!plain # assert_output <<-CONSOLE # STDOUT> 0123456789 # STDERR> ^- This is left aligned # STDOUT> 01234567890 # STDERR> ^- This is indented 2 characters. # CONSOLE # # @example Assert all content went to STDOUT # !!!plain # assert_output <<-CONSOLE # 0123456789 # ^- This is left aligned # 01234567890 # ^- This is indented 2 characters. # CONSOLE # # @param [String] exp_out The expected console output, optionally # annotated with stream markers. # @param [String] msg An explanatory message about why the test # failure is relevant. def assert_output(exp_out, msg='Output lines did not match') # Remove the minimal consistent indentation from the input; # useful for clean HEREDOCs. indentation = exp_out.lines.map { |line| line[/^ */].length }.min cleaned_exp = exp_out.gsub(/^ {#{indentation}}/, '') # Divide output based on expected destination out, err = cleaned_exp.lines.partition do |line| line !~ /^((STD)?ERR|2)> / end our_out, our_err, our_output = [ out.join, err.join, cleaned_exp ].map do |str| str.gsub(/^((STD)?(ERR|OUT)|[12])> /, '') end # Exercise assertions about output assert_equal our_output, (result.nil? ? '' : result.output), msg assert_equal our_out, (result.nil? ? '' : result.stdout), 'The contents of STDOUT did not match expectations' assert_equal our_err, (result.nil? ? '' : result.stderr), 'The contents of STDERR did not match expectations' end # Assert that the provided string does not match the provided regular expression, can pass optional message # @deprecated This is placed her for backwards compatability for tests that used Test::Unit::Assertions, # http://apidock.com/ruby/Test/Unit/Assertions/assert_no_match # def assert_no_match(regexp, string, msg=nil) assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.") msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" } assert(regexp !~ string, msg) end alias_method :assert_not_match, :assert_no_match end end end beaker-4.30.0/lib/beaker/dsl/helpers.rb000066400000000000000000000024171407603575700176210ustar00rootroot00000000000000# -*- coding: utf-8 -*- [ 'host', 'test', 'web', 'hocon' ].each do |lib| require "beaker/dsl/helpers/#{lib}_helpers" end module Beaker module DSL # Contains methods to help you manage and configure your SUTs. # Extensions, available in separate modules, enable you to configure and interact with puppet, facter # and hiera. See [the docs](/docs/how_to/the_beaker_dsl.md). # To mix this is into a class you need the following: # * a method *hosts* that yields any hosts implementing # {Beaker::Host}'s interface to act upon. # * a method *options* that provides an options hash, see {Beaker::Options::OptionsHash} # * a method *logger* that yields a logger implementing # {Beaker::Logger}'s interface. # * the module {Beaker::DSL::Roles} that provides access to the various hosts implementing # {Beaker::Host}'s interface to act upon # * the module {Beaker::DSL::Wrappers} the provides convenience methods for {Beaker::DSL::Command} creation # * a method *metadata* that yields a hash # # module Helpers include Beaker::DSL::Helpers::HostHelpers include Beaker::DSL::Helpers::TestHelpers include Beaker::DSL::Helpers::WebHelpers include Beaker::DSL::Helpers::HoconHelpers end end end beaker-4.30.0/lib/beaker/dsl/helpers/000077500000000000000000000000001407603575700172705ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/dsl/helpers/hocon_helpers.rb000066400000000000000000000076101407603575700224510ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'hocon/parser/config_document_factory' require 'hocon/config_value_factory' module Beaker module DSL module Helpers # Convenience methods for modifying and reading Hocon configs # # @note For usage guides for these methods, check these sources: # - {https://github.com/puppetlabs/beaker/tree/master/docs/how_to/use_hocon_helpers.md Beaker docs}. # - Beaker acceptance tests in +acceptance/tests/base/dsl/helpers/hocon_helpers_test.rb+ module HoconHelpers # Reads the given hocon file from a SUT # # @param [Host] host Host to get hocon file from. # @param [String] filename Name of the hocon file to get # # @raise ArgumentError if arguments are missing or incorrect # @return [Hocon::ConfigValueFactory] parsed hocon file def hocon_file_read_on(host, filename) if filename.nil? || filename.empty? raise ArgumentError, '#hocon_file_edit_on requires a filename' end file_contents = on(host, "cat #{filename}").stdout Hocon::Parser::ConfigDocumentFactory.parse_string(file_contents) end # Grabs the given hocon file from a SUT, allowing you to edit the file # just like you would a local one in the passed block. # # @note This method does not save the hocon file after editing. Our # recommended workflow for that is included in our example. If you'd # rather just save a file in-place on a SUT, then # {#hocon_file_edit_in_place_on} is a better method to use. # # @example Editing a value & saving as a new file # hocon_file_edit_on(hosts, 'hocon.conf') do |host, doc| # doc2 = doc.set_value('a.b', '[1, 2, 3, 4, 5]') # create_remote_file(host, 'hocon_latest.conf', doc2.render) # end # # @param [Host,Array] hosts Host (or an array of hosts) to # edit the hocon file on. # @param [String] filename Name of the file to edit. # @param [Proc] block Code to edit the hocon file. # # @yield [Host] Currently executing host. # @yield [Hocon::ConfigValueFactory] Doc to edit. Refer to # {https://github.com/puppetlabs/ruby-hocon#basic-usage Hocon's basic usage doc} # for info on how to use this object. # # @raise ArgumentError if arguments are missing or incorrect. # @return nil def hocon_file_edit_on(hosts, filename, &block) if not block_given? msg = 'DSL method `hocon_file_edit_on` provides a given block' msg << ' a hocon file to edit. No block was provided.' raise ArgumentError, msg end block_on hosts, {} do | host | doc = hocon_file_read_on(host, filename) yield host, doc end end # Grabs the given hocon file from a SUT, allowing you to edit the file # and have those edits saved in-place of the file on the SUT. # # @note that a the crucial difference between this & {#hocon_file_edit_on} # is that your Proc will need to return the # {#hocon_file_edit_on Hocon::ConfigValueFactory doc} # you want saved for the in-place save to work correctly. # # @note for info about parameters, please checkout {#hocon_file_edit_on}. # # @example setting an attribute & saving # hocon_file_edit_in_place_on(hosts, hocon_filename) do |host, doc| # doc.set_value('c.d', '[6, 2, 73, 4, 45]') # end # def hocon_file_edit_in_place_on(hosts, filename, &block) hocon_file_edit_on(hosts, filename) do |host, doc| content_doc = yield host, doc create_remote_file(host, filename, content_doc.render) end end end end end end beaker-4.30.0/lib/beaker/dsl/helpers/host_helpers.rb000066400000000000000000000740141407603575700223220ustar00rootroot00000000000000module Beaker module DSL module Helpers # Methods that help you interact and manage the state of your Beaker SUTs, these # methods do not require puppet to be installed to execute correctly module HostHelpers # @!macro [new] common_opts # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Boolean] :silent (false) Do not produce log output # @option opts [Array] :acceptable_exit_codes ([0]) An array # (or range) of integer exit codes that should be considered # acceptable. An error will be thrown if the exit code does not # match one of the values in this list. # @option opts [Boolean] :accept_all_exit_codes (false) Consider all # exit codes as passing. # @option opts [Boolean] :dry_run (false) Do not actually execute any # commands on the SUT # @option opts [String] :stdin (nil) Input to be provided during command # execution on the SUT. # @option opts [Boolean] :pty (false) Execute this command in a pseudoterminal. # @option opts [Boolean] :expect_connection_failure (false) Expect this command # to result in a connection failure, reconnect and continue execution. # @option opts [Hash{String=>String}] :environment ({}) These will be # treated as extra environment variables that should be set before # running the command. # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel. # The primary method for executing commands *on* some set of hosts. # # @param [Host, Array, String, Symbol] host One or more hosts to act upon, # or a role (String or Symbol) that identifies one or more hosts. # @param [String, Command] command The command to execute on *host*. # @param [Proc] block Additional actions or assertions. # @!macro common_opts # # @example Most basic usage # on hosts, 'ls /tmp' # # @example Allowing additional exit codes to pass # on agents, 'puppet agent -t', :acceptable_exit_codes => [0,2] # # @example Using the returned result for any kind of checking # if on(host, 'ls -la ~').stdout =~ /\.bin/ # ...do some action... # end # # @example Using TestCase helpers from within a test. # agents.each do |agent| # on agent, 'cat /etc/puppet/puppet.conf' do # assert_match stdout, /server = #{master}/, 'WTF Mate' # end # end # # @example Using a role (defined in a String) to identify the host # on "master", "echo hello" # # @example Using a role (defined in a Symbol) to identify the host # on :dashboard, "echo hello" # @return [Result] An object representing the outcome of *command*. # @raise [FailTest] Raises an exception if *command* obviously fails. def on(host, command, opts = {}, &block) block_on host, opts do | host | if command.is_a? String cmd_opts = {} #add any additional environment variables to the command if opts[:environment] cmd_opts['ENV'] = opts[:environment] end command_object = Command.new(command.to_s, [], cmd_opts) elsif command.is_a? Command if opts[:environment] command_object = command.clone command_object.environment = opts[:environment] else command_object = command end else msg = "DSL method `on` can only be called with a String or Beaker::Command" msg << " object as the command parameter, not #{command.class}." raise ArgumentError, msg end @result = host.exec(command_object, opts) # Also, let additional checking be performed by the caller. if block_given? case block.arity #block with arity of 0, just hand back yourself when 0 yield self #block with arity of 1 or greater, hand back the result object else yield @result end end @result end end # The method for executing commands on the default host # # @param [String, Command] command The command to execute on *host*. # @param [Proc] block Additional actions or assertions. # @!macro common_opts # # @example Most basic usage # shell 'ls /tmp' # # @example Allowing additional exit codes to pass # shell 'puppet agent -t', :acceptable_exit_codes => [0,2] # # @example Using the returned result for any kind of checking # if shell('ls -la ~').stdout =~ /\.bin/ # ...do some action... # end # # @example Using TestCase helpers from within a test. # shell('cat /etc/puppet/puppet.conf') do |result| # assert_match result.stdout, /server = #{master}/, 'WTF Mate' # end # # @return [Result] An object representing the outcome of *command*. # @raise [FailTest] Raises an exception if *command* obviously fails. def shell(command, opts = {}, &block) on(default, command, opts, &block) end # @deprecated # An proxy for the last {Beaker::Result#stdout} returned by # a method that makes remote calls. Use the {Beaker::Result} # object returned by the method directly instead. For Usage see # {Beaker::Result}. def stdout return nil if @result.nil? @result.stdout end # @deprecated # An proxy for the last {Beaker::Result#stderr} returned by # a method that makes remote calls. Use the {Beaker::Result} # object returned by the method directly instead. For Usage see # {Beaker::Result}. def stderr return nil if @result.nil? @result.stderr end # @deprecated # An proxy for the last {Beaker::Result#exit_code} returned by # a method that makes remote calls. Use the {Beaker::Result} # object returned by the method directly instead. For Usage see # {Beaker::Result}. def exit_code return nil if @result.nil? @result.exit_code end # Move a file from a remote to a local path # @note If using {Beaker::Host} for the hosts *scp* is not # required on the system as it uses Ruby's net/scp library. The # net-scp gem however is required (and specified in the gemspec). # # @param [Host, #do_scp_from] host One or more hosts (or some object # that responds like # {Beaker::Host#do_scp_from}. # @param [String] from_path A remote path to a file. # @param [String] to_path A local path to copy *from_path* to. # @!macro common_opts # # @return [Result] Returns the result of the SCP operation def scp_from host, from_path, to_path, opts = {} block_on host do | host | @result = host.do_scp_from(from_path, to_path, opts) @result.log logger @result end end # Move a local file to a remote host using scp # @note If using {Beaker::Host} for the hosts *scp* is not # required on the system as it uses Ruby's net/scp library. The # net-scp gem however is required (and specified in the gemspec. # When using SCP with Windows it will now auto expand path when # using `cygpath instead of failing or requiring full path # # @param [Host, #do_scp_to] host One or more hosts (or some object # that responds like # {Beaker::Host#do_scp_to}. # @param [String] from_path A local path to a file. # @param [String] to_path A remote path to copy *from_path* to. # @!macro common_opts # # @return [Result] Returns the result of the SCP operation def scp_to host, from_path, to_path, opts = {} block_on host do | host | if host['platform'] =~ /windows/ && to_path.match('`cygpath') result = on host, "echo #{to_path}" to_path = result.raw_output.chomp end @result = host.do_scp_to(from_path, to_path, opts) @result.log logger @result end end # Move a local file or directory to a remote host using rsync # @note rsync is required on the local host. # # @param [Host, #do_scp_to] host A host object that responds like # {Beaker::Host}. # @param [String] from_path A local path to a file or directory. # @param [String] to_path A remote path to copy *from_path* to. # @!macro common_opts # # @return [Result] Returns the result of the rsync operation def rsync_to host, from_path, to_path, opts = {} block_on host do | host | if host['platform'] =~ /windows/ && to_path.match('`cygpath') result = host.echo "#{to_path}" to_path = result.raw_output.chomp end @result = host.do_rsync_to(from_path, to_path, opts) @result end end # Copy a remote file to the local system and save it under a directory # meant for storing SUT files to be viewed in the event of test failures. # # Files are stored locally with the following structure: # ./// # # This can be used during the post-suite phase to persist relevant log # files from the SUTs so they can available with the test results # (without having to preserve the SUT host and SSH in after the fact). # # Example # # Archive the Puppet Server log file from the master ('abc123'), # and archive the Puppet Agent log file from the agent ('xyz098'): # # archive_file_from(master, '/var/log/puppetlabs/puppetserver.log') # archive_file_from(agent, '/var/log/puppetlabs/puppetagent.log') # # Results in the following files on the test runner: # # archive/sut-files/abc123/var/log/puppetlabs/puppetserver.log # archive/sut-files/xyz098/var/log/puppetlabs/puppetagent.log # # @param [Host] host A host object (or some object that can be passed to # #scp_from) # @param [String] from_path A remote absolute path on the host to copy. # @!macro common_opts # @option [String] archive_root The local directory to store the copied # file under. Defaults to # 'archive/sut-files'. # @option [String] archive_name The name of the archive to be copied to # archive_root. Defaults to # 'sut-files.tgz'. # # @return [Result] Returns the result of the #scp_from operation. def archive_file_from(host, from_path, opts = {}, archive_root = 'archive/sut-files', archive_name = 'sut-files.tgz') require 'minitar' filedir = File.dirname(from_path) targetdir = File.join(archive_root, host.hostname, filedir) # full path to check for existance later filename = "#{targetdir}/" + File.basename(from_path) FileUtils.mkdir_p(targetdir) scp_from(host, from_path, targetdir, opts) # scp_from does succeed on a non-existant file, checking if the file/folder actually exists if not File.exists?(filename) raise IOError, "No such file or directory - #{filename}" end create_tarball(archive_root, archive_name) end # @visibility private def create_tarball(path, archive_name) tgz = Zlib::GzipWriter.new(File.open(archive_name, 'wb')) Minitar.pack(path, tgz) end private :create_tarball # Deploy packaging configurations generated by # https://github.com/puppetlabs/packaging to a host. # # @note To ensure the repo configs are available for deployment, # you should run `rake pl:jenkins:deb_repo_configs` and # `rake pl:jenkins:rpm_repo_configs` on your project checkout # # @param [Host] host # @param [String] path The path to the generated repository config # files. ex: /myproject/pkg/repo_configs # @param [String] name A human-readable name for the repository # @param [String] version The version of the project, as used by the # packaging tools. This can be determined with # `rake pl:print_build_params` from the packaging # repo. # @deprecated no longer used in beaker, beaker-puppet, or beaker-pe # @visibility private def deploy_package_repo host, path, name, version host.deploy_package_repo path, name, version end # Create a remote file out of a string # @note This method uses Tempfile in Ruby's STDLIB as well as {#scp_to}. # # @param [Host, #do_scp_to] hosts One or more hosts (or some object # that responds like # {Beaker::Host#do_scp_from}. # @param [String] file_path A remote path to place *file_content* at. # @param [String] file_content The contents of the file to be placed. # @!macro common_opts # @option opts [String] :protocol Name of the underlying transfer method. # Valid options are 'scp' or 'rsync'. # # @return [Result] Returns the result of the underlying SCP operation. def create_remote_file(hosts, file_path, file_content, opts = {}) Tempfile.open 'beaker' do |tempfile| File.open(tempfile.path, 'w') {|file| file.puts file_content } opts[:protocol] ||= 'scp' case opts[:protocol] when 'scp' scp_to hosts, tempfile.path, file_path, opts when 'rsync' rsync_to hosts, tempfile.path, file_path, opts else logger.debug "Unsupported transfer protocol, returning nil" nil end end end # Execute a powershell script from file, remote file created from provided string # @note This method uses Tempfile in Ruby's STDLIB as well as {#create_remote_file}. # # @param [Host] hosts One or more hosts (or some object # that responds like # {Beaker::Host#do_scp_from}. # @param [String] powershell_script A string describing a set of powershell actions # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel. # # @return [Result] Returns the result of the powershell command execution def execute_powershell_script_on(hosts, powershell_script, opts = {}) block_on hosts, opts do |host| script_path = "beaker_powershell_script_#{Time.now.to_i}.ps1" create_remote_file(host, script_path, powershell_script, opts) native_path = script_path.gsub(/\//, "\\") on host, powershell("", {"File" => native_path }), opts end end # Move a local script to a remote host and execute it # @note this relies on {#on} and {#scp_to} # # @param [Host, #do_scp_to] host One or more hosts (or some object # that responds like # {Beaker::Host#do_scp_from}. # @param [String] script A local path to find an executable script at. # @!macro common_opts # @param [Proc] block Additional tests to run after script has executed # # @return [Result] Returns the result of the underlying SCP operation. def run_script_on(host, script, opts = {}, &block) # this is unsafe as it uses the File::SEPARATOR will be set to that # of the coordinator node. This works for us because we use cygwin # which will properly convert the paths. Otherwise this would not # work for running tests on a windows machine when the coordinator # that the harness is running on is *nix. We should use # {Beaker::Host#temp_path} instead. TODO remote_path = File.join("", "tmp", File.basename(script)) scp_to host, script, remote_path on host, remote_path, opts, &block end # Move a local script to default host and execute it # @see #run_script_on def run_script(script, opts = {}, &block) run_script_on(default, script, opts, &block) end # Install a package on a host # # @param [Host] host A host object # @param [String] package_name Name of the package to install # # @return [Result] An object representing the outcome of *install command*. def install_package host, package_name, package_version = nil host.install_package package_name, '', package_version end # Uninstall a package on a host # # @param [Host] host A host object # @param [String] package_name Name of the package to uninstall # # @return [Result] An object representing the outcome of *uninstall command*. def uninstall_package host, package_name host.uninstall_package package_name end # Check to see if a package is installed on a remote host # # @param [Host] host A host object # @param [String] package_name Name of the package to check for. # # @return [Boolean] true/false if the package is found def check_for_package host, package_name host.check_for_package package_name end # Upgrade a package on a host. The package must already be installed # # @param [Host] host A host object # @param [String] package_name Name of the package to install # # @return [Result] An object representing the outcome of *upgrade command*. def upgrade_package host, package_name host.upgrade_package package_name end # Configure a host entry on the give host # @example: will add a host entry for forge.puppetlabs.com # add_system32_hosts_entry(host, { :ip => '23.251.154.122', :name => 'forge.puppetlabs.com' }) # # @return nil def add_system32_hosts_entry(host, opts = {}) if host.is_powershell? hosts_file = 'C:\Windows\System32\Drivers\etc\hosts' host_entry = "#{opts['ip']}`t`t#{opts['name']}" on host, powershell("\$text = \\\"#{host_entry}\\\"; Add-Content -path '#{hosts_file}' -value \$text") else raise "nothing to do for #{host.name} on #{host['platform']}" end end # Back up the given file in the current_dir to the new_dir # # @param host [Beaker::Host] The target host # @param current_dir [String] The directory containing the file to back up # @param new_dir [String] The directory to copy the file to # @param filename [String] The file to back up. Defaults to 'puppet.conf' # # @return [String, nil] The path to the file if the file exists, nil if it # doesn't exist. def backup_the_file host, current_dir, new_dir, filename = 'puppet.conf' old_location = current_dir + '/' + filename new_location = new_dir + '/' + filename + '.bak' if host.file_exist? old_location host.exec( Command.new( "cp #{old_location} #{new_location}" ) ) return new_location else logger.warn "Could not backup file '#{old_location}': no such file" nil end end # Return a Hash with the Alternate Data Stream (ADS) name as well as the # base file path # # @param file_path [String] The full path with the ADS attached # # @return [Hash] The base file path and ADS name def win_ads_path(file_path) ads_path = { path: file_path, ads: nil } split_path = file_path.split(':') if split_path.size > 2 ads_path[:ads] = split_path.pop ads_path[:path] = split_path.join(':') end return ads_path end # Check whether a file exists on the host # # @param host [Beaker::Host] The target host # @param file_path [String] The absolute path of the file # # @return [Boolean] Whether the file exists on the host (using `test -f`) def file_exists_on(host, file_path) if host['platform'] =~ /windows/ command = %(Test-Path #{file_path}) if file_path.include?(':') split_path = win_ads_path(file_path) command = %(Test-Path #{split_path[:path]}) command += %( -AND Get-Item -path #{split_path[:path]} -stream #{split_path[:ads]}) if split_path[:ads] end command = powershell(command) else command = %(test -f "#{file_path}") end return on(host, command, { :accept_all_exit_codes => true}).exit_code.zero? end # Check whether a directory exists on the host # # @param host [Beaker::Host] The target host # @param dir_path [String] The absolute path of the directory # # @return [Boolean] Whether the directory exists on the host (using `test -d`) def directory_exists_on(host, dir_path) if host['platform'] =~ /windows/ dir_path = "#{dir_path}\\" unless (dir_path[-1].chr == '\\') command = Command.new(%{IF exist "#{dir_path}" ( exit 0 ) ELSE ( exit 1 )}, [], { :cmdexe => true }) else command = Command.new(%(test -d "#{dir_path}"), []) end return on(host, command, { :accept_all_exit_codes => true }).exit_code.zero? end # Check whether a symlink exists on the host # # @param host [Beaker::Host] The target host # @param link_path [String] The absolute path of the symlink # # @return [Boolean] Whether the symlink exists on the host (using `test -L`) def link_exists_on(host, link_path) # Links are weird on windows, fall back to seeing if the file exists return file_exists_on(host, link_path) if host['platform'] =~ /windows/ return on(host, Command.new(%(test -L "#{link_path}"), accept_all_exit_codes: true)).exit_code.zero? end # Get the contents of a file on the host # # @param host [Beaker::Host] The target host # @param file_path [String] The absoltue path to the file # # @return [String] The contents of the file def file_contents_on(host, file_path) file_contents = nil split_path = win_ads_path(file_path) if file_exists_on(host, split_path[:path]) if host['platform'] =~ /windows/ file_path.gsub!('/', '\\') command = %{Get-Content -Raw -Path #{file_path}} command += %{ -Stream #{split_path[:ads]}} if split_path[:ads] file_contents = on(host, powershell(command))&.stdout&.strip else file_contents = on(host, %(cat "#{file_path}"))&.stdout&.strip end else logger.warn("File '#{file_path}' on '#{host} does not exist") end return file_contents end # Run a curl command on the provided host(s) # # @param [Host, Array, String, Symbol] host One or more hosts to act upon, # or a role (String or Symbol) that identifies one or more hosts. # @param [String, Command] cmd The curl command to execute on *host*. # @param [Proc] block Additional actions or assertions. # @!macro common_opts # def curl_on(host, cmd, opts = {}, &block) on host, "curl --tlsv1 %s" % cmd, opts, &block end def curl_with_retries(desc, host, url, desired_exit_codes, max_retries = 60, retry_interval = 1) opts = { :desired_exit_codes => desired_exit_codes, :max_retries => max_retries, :retry_interval => retry_interval } retry_on(host, "curl -m 1 #{url}", opts) end # This command will execute repeatedly until success or it runs out with an error # # @param [Host, Array, String, Symbol] host One or more hosts to act upon, # or a role (String or Symbol) that identifies one or more hosts. # @param [String, Command] command The command to execute on *host*. # @param [Hash{Symbol=>String}] opts Options to alter execution. # @param [Proc] block Additional actions or assertions. # # @option opts [Array, Fixnum] :desired_exit_codes (0) An array # or integer exit code(s) that should be considered # acceptable. An error will be thrown if the exit code never # matches one of the values in this list. # @option opts [Fixnum] :max_retries (60) number of times the # command will be tried before failing # @option opts [Float] :retry_interval (1) number of seconds # that we'll wait between tries # @option opts [Boolean] :verbose (false) # # @return [Result] Result object of the last command execution def retry_on(host, command, opts = {}, &block) option_exit_codes = opts[:desired_exit_codes] option_max_retries = opts[:max_retries].to_i option_retry_interval = opts[:retry_interval].to_f desired_exit_codes = option_exit_codes ? [option_exit_codes].flatten : [0] desired_exit_codes = [0] if desired_exit_codes.empty? max_retries = option_max_retries == 0 ? 60 : option_max_retries # nil & "" both return 0 retry_interval = option_retry_interval == 0 ? 1 : option_retry_interval verbose = true.to_s == opts[:verbose] log_prefix = host.log_prefix logger.debug "\n#{log_prefix} #{Time.new.strftime('%H:%M:%S')}$ #{command}" logger.debug " Trying command #{max_retries} times." logger.debug ".", add_newline=false result = on host, command, {:accept_all_exit_codes => true, :silent => !verbose}, &block num_retries = 0 until desired_exit_codes.include?(result.exit_code) sleep retry_interval result = on host, command, {:accept_all_exit_codes => true, :silent => !verbose}, &block num_retries += 1 logger.debug ".", add_newline=false if (num_retries > max_retries) logger.debug " Command \`#{command}\` failed." fail("Command \`#{command}\` failed.") end end logger.debug "\n#{log_prefix} #{Time.new.strftime('%H:%M:%S')}$ #{command} ostensibly successful." result end # FIX: this should be moved into host/platform # @visibility private def run_cron_on(host, action, user, entry="", &block) block_on host do | host | platform = host['platform'] if platform.include?('solaris') || platform.include?('aix') then case action when :list then args = '-l' when :remove then args = '-r' when :add on( host, "echo '#{entry}' > /var/spool/cron/crontabs/#{user}", &block ) end else # default for GNU/Linux platforms case action when :list then args = '-l -u' when :remove then args = '-r -u' when :add on( host, "echo '#{entry}' > /tmp/#{user}.cron && " + "crontab -u #{user} /tmp/#{user}.cron", &block ) end end if args case action when :list, :remove then on(host, "crontab #{args} #{user}", &block) end end end end # 'echo' the provided value on the given host(s) # @param [Host, Array, String, Symbol] hosts One or more hosts to act upon, # or a role (String or Symbol) that identifies one or more hosts. # @param [String] val The string to 'echo' on the host(s) # @return [String, Array The echo'ed value(s) returned by the host(s) def echo_on hosts, val block_on hosts do |host| if host.is_powershell? host.exec(Command.new("echo #{val}")).stdout.chomp else host.exec(Command.new("echo \"#{val}\"")).stdout.chomp end end end end end end end beaker-4.30.0/lib/beaker/dsl/helpers/test_helpers.rb000066400000000000000000000045311407603575700223210ustar00rootroot00000000000000module Beaker module DSL module Helpers # Methods that help you query the state of your tests, these # methods do not require puppet to be installed to execute correctly module TestHelpers # Gets the currently executing test's name, which is set in a test # using the {Beaker::DSL::Structure#test_name} method. # # @return [String] Test name, or nil if it hasn't been set def current_test_name() metadata[:case] && metadata[:case][:name] ? metadata[:case][:name] : nil end # Gets the currently executing test's filename, which is set from the # +@path+ variable passed into the {Beaker::TestCase#initialize} method, # not including the '.rb' extension # # @example if the path variable was man/plan/canal.rb, then the filename would be: # canal # # @return [String] Test filename, or nil if it hasn't been set def current_test_filename() metadata[:case] && metadata[:case][:file_name] ? metadata[:case][:file_name] : nil end # Gets the currently executing test's currently executing step name. # This is set using the {Beaker::DSL::Structure#step} method. # # @return [String] Step name, or nil if it hasn't been set def current_step_name() metadata[:step] && metadata[:step][:name] ? metadata[:step][:name] : nil end # Sets the currently executing test's name. # # @param [String] name Name of the test # # @return nil # @api private def set_current_test_name(name) metadata[:case] ||= {} metadata[:case][:name] = name end # Sets the currently executing test's filename. # # @param [String] filename Name of the file being tested # # @return nil # @api private def set_current_test_filename(filename) metadata[:case] ||= {} metadata[:case][:file_name] = filename end # Sets the currently executing step's name. # # @param [String] name Name of the step # # @return nil # @api private def set_current_step_name(name) metadata[:step] ||= {} metadata[:step][:name] = name end end end end end beaker-4.30.0/lib/beaker/dsl/helpers/web_helpers.rb000066400000000000000000000112671407603575700221230ustar00rootroot00000000000000module Beaker module DSL module Helpers # Convenience methods for checking links and moving web content to hosts module WebHelpers # Blocks until the port is open on the host specified, returns false # on failure def port_open_within?( host, port = 8140, seconds = 120 ) repeat_for( seconds ) do host.port_open?( port ) end end #Determine is a given URL is accessible #@param [String] link The URL to examine #@param [Integer] limit redirect limit, will follow redirects that many times #@return [Boolean] true if the ultimate URL after following redirects (301&302) has a '200' HTTP response code, false otherwise #@example # extension = link_exists?("#{URL}.tar.gz") ? ".tar.gz" : ".tar" def link_exists?(link, limit=10) begin require "net/http" require "net/https" require "open-uri" url = URI.parse(link) http = Net::HTTP.new(url.host, url.port) http.use_ssl = (url.scheme == 'https') http.verify_mode = (OpenSSL::SSL::VERIFY_NONE) response = http.start { |http| http.head(url.request_uri) } if (['301', '302'].include? response.code) && limit > 0 logger.debug("#{__method__} following #{response.code} to #{response['location']}") link_exists?(response['location'], limit - 1) else response.code == "200" end rescue return false end end # Fetch file_name from the given base_url into dst_dir. # # @param [String] base_url The base url from which to recursively download # files. # @param [String] file_name The trailing name component of both the source url # and the destination file. # @param [String] dst_dir The local destination directory. # # @return [String] dst The name of the newly-created file. # # @!visibility private def fetch_http_file(base_url, file_name, dst_dir) require 'open-uri' require 'open_uri_redirections' FileUtils.makedirs(dst_dir) base_url.chomp!('/') src = "#{base_url}/#{file_name}" dst = File.join(dst_dir, file_name) if options[:cache_files_locally] && File.exists?(dst) logger.notify "Already fetched #{dst}" else logger.notify "Fetching: #{src}" logger.notify " and saving to #{dst}" begin open(src, :allow_redirections => :all) do |remote| File.open(dst, "w") do |file| FileUtils.copy_stream(remote, file) end end rescue OpenURI::HTTPError => e if e.message =~ /404.*/ raise "Failed to fetch_remote_file '#{src}' (#{e.message})" else raise e end end end return dst end # Recursively fetch the contents of the given http url, ignoring # `index.html` and `*.gif` files. # # @param [String] url The base http url from which to recursively download # files. # @param [String] dst_dir The local destination directory. # # @return [String] dst The name of the newly-created subdirectory of # dst_dir. # # @!visibility private def fetch_http_dir(url, dst_dir) logger.notify "fetch_http_dir (url: #{url}, dst_dir #{dst_dir})" if url[-1, 1] !~ /\// url += '/' end url = URI.parse(url) chunks = url.path.split('/') dst = File.join(dst_dir, chunks.last) #determine directory structure to cut #only want to keep the last directory, thus cut total number of dirs - 2 (hostname + last dir name) cut = chunks.length - 2 wget_command = "wget -nv -P #{dst_dir} --reject \"index.html*\",\"*.gif\" --cut-dirs=#{cut} -np -nH --no-check-certificate -r #{url}" logger.notify "Fetching remote directory: #{url}" logger.notify " and saving to #{dst}" logger.notify " using command: #{wget_command}" stdout_and_stderr_str, status = Open3.capture2e(wget_command) stdout_and_stderr_str.each_line do |line| logger.debug(line) end unless status.success? raise "Failed to fetch_remote_dir '#{url}' (exit code #{$?})" end dst end end end end end beaker-4.30.0/lib/beaker/dsl/install_utils.rb000066400000000000000000000001721407603575700210410ustar00rootroot00000000000000module Beaker module DSL # Collection of installation methods and support module InstallUtils end end end beaker-4.30.0/lib/beaker/dsl/outcomes.rb000066400000000000000000000070461407603575700200200ustar00rootroot00000000000000module Beaker module DSL # This module includes dsl helpers for setting the state of a test case. # They do not need inclusion if using third party test runner. The # Exception classes that they raise however should be defined as other # DSL helpers will raise them as needed. See individual DSL modules # for their specific dependencies. A class that mixes in this module # must have a method #logger which will yield an object that responds to # #notify and #warn. NOTE: the interface to logger may change shortly and # {Beaker::Logger} should be consulted for the appropriate # interface. # # Simply these methods log a message and raise the appropriate Exception # The exceptions are are caught by {Beaker::TestCase} and are # designed to allow some degree of freedom from the individual third # party test runners that could be used. module Outcomes # Raise this class if it is determined that a test case should not # be executed because the feature in question is still a # "Work in Progress" class PendingTest < Exception; end # Raise this class if execution should be stopped because the test # is not applicable within a given environment. class SkipTest < Exception; end # Raise this class if some criteria has been met that proves a failure. class FailTest < Exception; end # Raise this class if execution should stop because enough criteria has # shown itself to pass the test. class PassTest < Exception; end # Raises FailTest Exception and logs an error message # # @param [String] msg An optional message to log # @raise [FailTest] def fail_test msg = nil message = formatted_message( msg, 'Failed' ) logger.warn( [message, logger.pretty_backtrace].join("\n") ) raise( FailTest, message ) end # Raises PassTest Exception and logs a message # # @param [String] msg An optional message to log # @raise [PassTest] def pass_test msg = nil message = formatted_message( msg, 'Passed' ) logger.notify( message ) raise( PassTest, message ) end # Raises PendingTest Exception and logs an error message # # @param [String] msg An optional message to log # @raise [PendingTest] def pending_test msg = nil message = formatted_message( msg, 'is Pending' ) logger.warn( message ) raise( PendingTest, message ) end # Raises SkipTest Exception and logs a message # # @param [String] msg An optional message to log # @raise [SkipTest] def skip_test msg = nil message = formatted_message( msg, 'was Skipped' ) logger.notify( message ) raise( SkipTest, message ) end # populate a TestCase's @exports[] with structured_data # # @param [Hash,Array] data The data to export def export(data) @exports << data end # Formats an optional message or self appended by a state, either # bracketted in newlines # # @param [String, nil] message The message (or nil) to format # @param [String] default_str The string to be appended to self if # message is nil # # @return [String] A prettier string with helpful info # @!visibility private def formatted_message(message, default_str ) msg = message ? "\n#{message}\n" : "\n#{self} #{default_str}.\n" return msg end end end end beaker-4.30.0/lib/beaker/dsl/patterns.rb000066400000000000000000000035711407603575700200210ustar00rootroot00000000000000module Beaker module DSL # These are simple patterns that appear frequently in beaker test # code, and are provided to simplify test construction. # # # It requires the class it is mixed into to provide the attribute # `hosts` which contain the hosts to search, these should implement # {Beaker::Host}'s interface. They, at least, must have #[] # and #to_s available and provide an array when #[]('roles') is called. # module Patterns # Execute a block selecting the hosts that match with the provided criteria # @param [Array, Host, String, Symbol] hosts_or_filter A host role as a String or Symbol that can be # used to search for a set of Hosts, a host name # as a String that can be used to search for # a set of Hosts, or a {Host} # or Array<{Host}> to run the block against # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel. # @param [Block] block This method will yield to a block of code passed by the caller # # @return [Array, Result, nil] An array of results, a result object, or nil. # Check {Beaker::Shared::HostManager#run_block_on} for more details on this. def block_on hosts_or_filter, opts={}, &block block_hosts = nil if defined? hosts block_hosts = hosts end filter = nil if hosts_or_filter.is_a? String or hosts_or_filter.is_a? Symbol filter = hosts_or_filter else block_hosts = hosts_or_filter end run_block_on block_hosts, filter, opts, &block end end end end beaker-4.30.0/lib/beaker/dsl/roles.rb000066400000000000000000000216541407603575700173070ustar00rootroot00000000000000module Beaker module DSL # # Identifying hosts. # # This aids in reaching common subsets of hosts in a testing matrix. # # It requires the class it is mixed into to provide the attribute # `hosts` which contain the hosts to search, these should implement # {Beaker::Host}'s interface. They, at least, must have #[] # and #to_s available and provide an array when #[]('roles') is called. # # Also the constant {FailTest} needs to be defined it will be raised # in error conditions # module Roles # The hosts for which ['roles'] include 'agent' # # @return [Array] May be empty # # @example Basic usage # agents.each do |agent| # ...test each agent in turn... # end # def agents hosts_as 'agent' end # The host for which ['roles'] include 'master'. # If no host has the 'master' role, then use the host defined as 'default'. # If no host is defined as a 'master' and there is no 'default' host defined # then it either raises an error (has a master), # or it returns nil (masterless) # # @return [Host] Returns the host, or nil if not found & masterless # @raise [Beaker::DSL::Outcomes::FailTest] if there are less # or more than 1 master is found. # # @example Basic usage # on, master, 'cat /etc/puppet/puppet.conf' # def master find_host_with_role :master end # The host for which ['roles'] include 'database' # # @return [Host] Returns the host, or nil if not found & masterless # @raise [Beaker::DSL::Outcomes::FailTest] if there are an inappropriate # number of hosts found (depends on masterless option) # # @example Basic usage # on, agent, "curl -k http://#{database}:8080" # def database find_host_with_role :database end # The host for which ['roles'] include 'dashboard' # # @return [Host] Returns the host, or nil if not found & masterless # @raise [Beaker::DSL::Outcomes::FailTest] if there are an inappropriate # number of hosts found (depends on masterless option) # # @example Basic usage # on, agent, "curl https://#{database}/nodes/#{agent}" # def dashboard find_host_with_role :dashboard end # The default host # - if only one host defined, then that host # OR # - host with 'default' as a role # OR # - host with 'master' as a role # # @return [Host] Returns the host, or nil if not found & masterless # @raise [Beaker::DSL::Outcomes::FailTest] if there are an inappropriate # number of hosts found (depends on masterless option) # # @example Basic usage # on, default, "curl https://#{database}/nodes/#{agent}" # def default find_host_with_role :default end # Determine if host is not a controller, does not have roles 'master', # 'dashboard' or 'database'. # # @return [Boolean] True if agent-only, false otherwise # # @example Basic usage # if not_controller(host) # puts "this host isn't in charge!" # end # def not_controller(host) controllers = ['dashboard', 'database', 'master', 'console'] matched_roles = host['roles'].select { |v| controllers.include?(v) } matched_roles.length == 0 end # Determine if this host is exclusively an agent (only has a single role 'agent') # # @param [Host] host Beaker host to check # # @example Basic usage # if agent_only(host) # puts "this host is ONLY an agent!" # end # # @return [Boolean] True if agent-only, false otherwise def agent_only(host) host['roles'].length == 1 && host['roles'].include?('agent') end # Determine whether a host has an AIO version or not. If a host :pe_ver or # :version is not specified, then either the 'aio' role or type will be # needed for a host to be the AIO version. # # True if host has # * PE version (:pe_ver) >= 4.0 # * FOSS version (:version) >= 4.0 # * the role 'aio' # * the type 'aio' # # @note aio version is just a base-line condition. If you want to check # that a host is an aio agent, refer to {#aio_agent?}. # # @return [Boolean] whether or not a host is AIO-capable def aio_version?(host) [:pe_ver, :version].each do |key| version = host[key] return !version_is_less(version, '4.0') if version && !version.empty? end return true if host[:roles] && host[:roles].include?('aio') return true if host[:type] && !!(host[:type] =~ /(\A|-)aio(\Z|-)/ ) false end # Determine if the host is an AIO agent # # @param [Host] host Beaker host to check # # @return [Boolean] whether this host is an AIO agent or not def aio_agent?(host) aio_version?(host) && agent_only(host) end # Add the provided role to the host # # @param [Host] host Host to add role to # @param [String] role The role to add to host def add_role(host, role) host[:roles] = host[:roles] | [role] end #Create a new role method for a given arbitrary role name. Makes it possible to be able to run #commands without having to refer to role by String or Symbol. Will not add a new method #definition if the name is already in use. # @param [String, Symbol, Array[String,Symbol]] role The role that you wish to create a definition for, either a String # Symbol or an Array of Strings or Symbols. # @example Basic usage # add_role_def('myrole') # on myrole, "run command" def add_role_def role if role.kind_of?(Array) role.each do |r| add_role_def r end else if not respond_to? role if role !~ /\A[[:alpha:]]+[a-zA-Z0-9_]*[!?=]?\Z/ raise ArgumentError, "Role name format error for '#{role}'. Allowed characters are: \na-Z\n0-9 (as long as not at the beginning of name)\n'_'\n'?', '!' and '=' (only as individual last character at end of name)" end self.class.send :define_method, role.to_s do hosts_with_role = hosts_as role.to_sym if hosts_with_role.length == 1 hosts_with_role = hosts_with_role.pop end hosts_with_role end end end end # Determine if there is a host or hosts with the given role defined # @return [Boolean] True if there is a host with role, false otherwise # # @example Usage # if any_hosts_as?(:master) # puts "master is defined" # end # def any_hosts_as?(role) hosts_as(role).length > 0 end # Select hosts that include a desired role from #hosts # # @param [String, Symbol] desired_role The role to select for # @return [Array] The hosts that match # desired_role, may be empty # # @example Basic usage # hairy = hosts_as :yak # hairy.each do |yak| # on yak, 'shave' # end # def hosts_as(desired_role = nil) hosts_with_role(hosts, desired_role) end # finds the appropriate number of hosts for a given role # determines whether to allow no server using the masterless option # # @param [Symbol, String] role The role to find a host for # @return [Host] Returns the host, or nil if masterless and none are found # for that role # @raise Throws an exception if an inappropriate number of hosts are found # for that role def find_host_with_role role if (defined? options) && options[:masterless] find_at_most_one role else find_only_one role end end # @param [Symbol, String] role The role to find a host for # @return [Host] Returns the host, if one and only one is found # @raise Raises a failure exception if one and only one host that matches # the specified role is NOT found. def find_only_one role only_host_with_role(hosts, role) rescue ArgumentError => e raise DSL::Outcomes::FailTest, e.to_s end # @param [Symbol, String] role The role to find a host for # @return [Host] Returns the host, or nil if not found # @raise Raises a failure exception if more than one host that matches # the specified role is found. def find_at_most_one role find_at_most_one_host_with_role(hosts, role) rescue ArgumentError => e raise DSL::Outcomes::FailTest, e.to_s end end end end beaker-4.30.0/lib/beaker/dsl/structure.rb000066400000000000000000000372201407603575700202170ustar00rootroot00000000000000require 'beaker/dsl/assertions' module Beaker module DSL # These are simple structural elements necessary for writing # understandable tests and ensuring cleanup actions happen. If using a # third party test runner they are unnecessary. # # To include this in your own test runner a method #logger should be # available to yield a logger that implements # {Beaker::Logger}'s interface. As well as a method # #teardown_procs that yields an array. # # @example Structuring a test case. # test_name 'Look at me testing things!' do # teardown do # ...clean up actions... # end # # step 'Prepare the things' do # ...setup steps... # end # # step 'Test the things' do # ...tests... # end # # step 'Expect this to fail' do # expect_failure('expected to fail due to PE-1234') do # assert_equal(400, response.code, 'bad response code from API call') # end # end # module Structure require 'pry' # Provides a method to help structure tests into coherent steps. # @param [String] step_name The name of the step to be logged. # @param [Proc] block The actions to be performed in this step. def step step_name, &block logger.notify "\n* #{step_name}\n" set_current_step_name(step_name) if block_given? begin logger.with_indent do yield end rescue Exception => e if(@options.has_key?(:debug_errors) && @options[:debug_errors] == true) logger.info("Exception raised during step execution and debug-errors option is set, entering pry. Exception was: #{e.inspect}") logger.info("HINT: Use the pry 'backtrace' and 'up' commands to navigate to the test code") binding.pry end raise e end end end # Provides a method to help manual tests. So we can use beaker to set up # the environment, then prompt a user to manually check the setup. # @param [String] step_name The name of the step to be logged. def manual_step step_name require 'readline' logger.notify "\n* #{step_name}\n" if(!@options.has_key?(:exec_manual_tests)) # if the option -exec-manual-tests is not set then this has executed outside of a manual tests # so we raise an error to avoid issues raise('--exec-manual-tests option not set, this means a manual_step was used outside a manual_test') end set_current_step_name(step_name) # Here we prompt the user to tell us if the step passed or failed loop do input = Readline.readline('Did this step pass, Y/n? ', true).squeeze(" ").strip.downcase if %w(y yes).include?(input) break elsif %w(n no).include?(input) # if the step failed, the user can enter a fail message. # we loops to ensure they give use a fail message fail_message = '' loop do fail_message = Readline.readline('What was the reason for failure? ', true).squeeze(" ").strip if fail_message == '' # if nothing is entered we tell the user to enter something puts "No reason for failure given, please enter reason for failure." else break end end raise Beaker::DSL::FailTest, fail_message else # if something other than Y or n is returned we ask them again puts "Please enter Y or n." end end end # Provides a method to mark manual tests. # If the --exec-manual-tests param is not set then we skip the test # this is so manual tests do not execute by mistake # @param [String] manual_test_name The name of the test to be logged. # @param [Proc] block The actions to be performed during this test. # def manual_test manual_test_name, &block if(@options.has_key?(:exec_manual_tests) && @options[:exec_manual_tests] == true) # here the option is set so we run the test as normal test_name manual_test_name, &block else # here no option was set so we log the test name and skip it test_name manual_test_name raise( Beaker::DSL::SkipTest, '--exec-manual-tests option not set, so skipping manual test' ) end end # Provides a method to name tests. # # @param [String] my_name The name of the test to be logged. # @param [Proc] block The actions to be performed during this test. # def test_name my_name, &block logger.notify "\n#{my_name}\n" set_current_test_name(my_name) if block_given? logger.with_indent do yield end end end # Declare a teardown process that will be called after a test case is # complete. # # @param block [Proc] block of code to execute during teardown # @example Always remove /etc/puppet/modules # teardown do # on(master, puppet_resource('file', '/etc/puppet/modules', # 'ensure=absent', 'purge=true')) # end def teardown &block @teardown_procs << block end # Wrap an assert that is supposed to fail due to a product bug, an # undelivered feature, or some similar situation. # # This converts failing asserts into passing asserts (so we can continue to # run the test even though there are underlying product bugs), and converts # passing asserts into failing asserts (so we know when the underlying product # bug has been fixed). # # Pass an assert as a code block, and pass an explanatory message as a # parameter. The assert's logic will be inverted (so passes turn into fails # and fails turn into passes). # # @example Typical usage # expect_failure('expected to fail due to PE-1234') do # assert_equal(400, response.code, 'bad response code from API call') # end # # @example Output when a product bug would normally cause the assert to fail # Warning: An assertion was expected to fail, and did. # This is probably due to a known product bug, and is probably not a problem. # Additional info: 'expected to fail due to PE-6995' # Failed assertion: 'bad response code from API call. # <400> expected but was <409>.' # # @example Output when the product bug has been fixed # # # @param [String] explanation A description of why this assert is expected to # fail # @param block [Proc] block of code is expected to either raise an # {Beaker::Assertions} or else return a value that # will be ignored # @raise [RuntimeError] if the code block passed to this method does not raise # a {Beaker::Assertions} (i.e., if the assert # passes) # @author Chris Cowell-Shah (ccs@puppetlabs.com) def expect_failure(explanation, &block) begin yield if block_given? # code block should contain an assert that you expect to fail rescue Beaker::DSL::Assertions, Minitest::Assertion => failed_assertion # Yay! The assert in the code block failed, as expected. # Swallow the failure so the test passes. logger.notify 'An assertion was expected to fail, and did. ' + 'This is probably due to a known product bug, ' + 'and is probably not a problem. ' + "Additional info: '#{explanation}' " + "Failed assertion: '#{failed_assertion}'" return end # Uh-oh! The assert in the code block unexpectedly passed. fail('An assertion was expected to fail, but passed. ' + 'This is probably because a product bug was fixed, and ' + '"expect_failure()" needs to be removed from this test. ' + "Additional info: '#{explanation}'") end # Limit the hosts a test case is run against # @note This will modify the {Beaker::TestCase#hosts} member # in place unless an array of hosts is passed into it and # {Beaker::TestCase#logger} yielding an object that responds # like {Beaker::Logger#warn}, as well as # {Beaker::DSL::Outcomes#skip_test}, and optionally # {Beaker::TestCase#hosts}. # # @param [Symbol] type The type of confinement to do. Valid parameters # are *:to* to confine the hosts to only those that # match *criteria* or *:except* to confine the test # case to only those hosts that do not match # criteria. # @param [Hash{Symbol,String=>String,Regexp,Array}] # criteria Specify the criteria with which a host should be # considered for inclusion or exclusion. The key is any attribute # of the host that will be yielded by {Beaker::Host#[]}. # The value can be any string/regex or array of strings/regexp. # The values are compared using [Enumerable#any?] so that if one # value of an array matches the host is considered a match for that # criteria. # @param [Array] host_array This creatively named parameter is # an optional array of hosts to confine to. If not passed in, this # method will modify {Beaker::TestCase#hosts} in place. # @param [Proc] block Addition checks to determine suitability of hosts # for confinement. Each host that is still valid after checking # *criteria* is then passed in turn into this block. The block # should return true if the host matches this additional criteria. # # @example Basic usage to confine to debian OSes. # confine :to, :platform => 'debian' # # @example Confining to anything but Windows and Solaris # confine :except, :platform => ['windows', 'solaris'] # # @example Using additional block to confine to Solaris global zone. # confine :to, :platform => 'solaris' do |solaris| # on( solaris, 'zonename' ) =~ /global/ # end # # @example Confining to an already defined subset of hosts # confine :to, {}, agents # # @example Confining from an already defined subset of hosts # confine :except, {}, agents # # @example Confining to all ubuntu agents + all non-agents # confine :to, { :platform => 'ubuntu' }, agents # # @example Confining to any non-windows agents + all non-agents # confine :except, { :platform => 'windows' }, agents # # # @return [Array] Returns an array of hosts that are still valid # targets for this tests case. # @raise [SkipTest] Raises skip test if there are no valid hosts for # this test case after confinement. def confine(type, criteria, host_array = nil, &block) hosts_to_modify = Array( host_array || hosts ) hosts_not_modified = hosts - hosts_to_modify #we aren't examining these hosts case type when :except if criteria and ( not criteria.empty? ) hosts_to_modify = hosts_to_modify - select_hosts(criteria, hosts_to_modify, &block) + hosts_not_modified else # confining to all hosts *except* provided array of hosts hosts_to_modify = hosts_not_modified end if hosts_to_modify.empty? logger.warn "No suitable hosts without: #{criteria.inspect}" skip_test "No suitable hosts found without #{criteria.inspect}" end when :to if criteria and ( not criteria.empty? ) hosts_to_modify = select_hosts(criteria, hosts_to_modify, &block) + hosts_not_modified else # confining to only hosts in provided array of hosts end if hosts_to_modify.empty? logger.warn "No suitable hosts with: #{criteria.inspect}" skip_test "No suitable hosts found with #{criteria.inspect}" end else raise "Unknown option #{type}" end self.hosts = hosts_to_modify hosts_to_modify end # Ensures that host restrictions as specifid by type, criteria and # host_array are confined to activity within the passed block. # TestCase#hosts is reset after block has executed. # # @see #confine def confine_block(type, criteria, host_array = nil, &block) host_array = Array( host_array || hosts ) original_hosts = self.hosts.dup confine(type, criteria, host_array) yield rescue Beaker::DSL::Outcomes::SkipTest => e # I don't like this much, but adding options to confine is a breaking change # to the DSL that would involve a major version bump if e.message !~ /No suitable hosts found/ # a skip generated from the provided block, pass it up the chain raise e end ensure self.hosts = original_hosts end #Return a set of hosts that meet the given criteria # @param [Hash{Symbol,String=>String,Regexp,Array}] # criteria Specify the criteria with which a host should be # considered for inclusion. The key is any attribute # of the host that will be yielded by {Beaker::Host#[]}. # The value can be any string/regex or array of strings/regexp. # The values are compared using [Enumerable#any?] so that if one # value of an array matches the host is considered a match for that # criteria. # @param [Array] host_array This creatively named parameter is # an optional array of hosts to confine to. If not passed in, this # method will modify {Beaker::TestCase#hosts} in place. # @param [Proc] block Addition checks to determine suitability of hosts # for selection. Each host that is still valid after checking # *criteria* is then passed in turn into this block. The block # should return true if the host matches this additional criteria. # # @return [Array] Returns an array of hosts that meet the provided criteria def select_hosts(criteria, host_array = nil, &block) hosts_to_select_from = host_array || hosts criteria.each_pair do |property, value| hosts_to_select_from = hosts_to_select_from.select do |host| inspect_host host, property, value end end if block_given? hosts_to_select_from = hosts_to_select_from.select do |host| yield host end end hosts_to_select_from end # @!visibility private def inspect_host(host, property, one_or_more_values) values = Array(one_or_more_values) return values.any? do |value| true_false = false case value when String true_false = host[property.to_s].include? value when Regexp true_false = host[property.to_s] =~ value end true_false end end end end end beaker-4.30.0/lib/beaker/dsl/test_tagging.rb000066400000000000000000000135101407603575700206320ustar00rootroot00000000000000module Beaker module DSL # Test Tagging is about applying meta-data to tests (using the #tag method), # so that you can control which tests are executed in a particular beaker # run at a more fine-grained level. # # @note There are a few places where TestTagging-related code is located: # - {Beaker::Options::Parser#normalize_tags!} makes sure the test tags # are formatted correctly for use in this module # - {Beaker::Options::CommandLineParser#initialize} parses test tagging # options # - {Beaker::Options::Validator#validate_tags} ensures test tag CLI params # are valid for use by this module module TestTagging # Sets tags on the current {Beaker::TestCase}, and skips testing # if necessary after checking this case's tags against the ones that are # being included or excluded. # # @param [Array] tags Tags to be assigned to the current test # # @return nil # @api public def tag(*tags) metadata[:case] ||= {} metadata[:case][:tags] = [] tags.each do |tag| metadata[:case][:tags] << tag.downcase end @options[:test_tag_and] ||= [] @options[:test_tag_or] ||= [] @options[:test_tag_exclude] ||= [] tags_needed_to_include_this_test = [] @options[:test_tag_and].each do |tag_to_include| tags_needed_to_include_this_test << tag_to_include \ unless metadata[:case][:tags].include?(tag_to_include) end skip_test "#{self.path} does not include necessary tag(s): #{tags_needed_to_include_this_test}" \ if tags_needed_to_include_this_test.length > 0 found_test_tag = false @options[:test_tag_or].each do |tag_to_include| found_test_tag = metadata[:case][:tags].include?(tag_to_include) break if found_test_tag end skip_test "#{self.path} does not include any of these tag(s): #{@options[:test_tag_or]}" \ if @options[:test_tag_or].length > 0 && !found_test_tag tags_to_remove_to_include_this_test = [] @options[:test_tag_exclude].each do |tag_to_exclude| tags_to_remove_to_include_this_test << tag_to_exclude \ if metadata[:case][:tags].include?(tag_to_exclude) end skip_test "#{self.path} includes excluded tag(s): #{tags_to_remove_to_include_this_test}" \ if tags_to_remove_to_include_this_test.length > 0 platform_specific_tag_confines end # Handles platform-specific tag confines logic # # @return nil # @!visibility private def platform_specific_tag_confines @options[:platform_tag_confines_object] ||= PlatformTagConfiner.new( @options[:platform_tag_confines] ) confines = @options[:platform_tag_confines_object].confine_details( metadata[:case][:tags] ) confines.each do |confine_details| logger.notify( confine_details[:log_message] ) confine( confine_details[:type], :platform => confine_details[:platform_regex] ) end end class PlatformTagConfiner # Constructs the PlatformTagConfiner, transforming the user format # into the internal structure for use by Beaker itself. # # @param [ArrayObject}>] platform_tag_confines_array # The array of PlatformTagConfines objects that specify how these # confines should behave. See the note below for more info # # @note PlatformTagConfines objects come in the form # [ # { # :platform => , # :tag_reason_hash => { # => , # => , # ...etc... # } # } # ] # # Internally, we want to turn tag matches into platform # confine statements. So a better internal structure would # be something of the form: # { # => [{ # :platform => , # :reason => , # :type => :except, # }, ... ] # } def initialize(platform_tag_confines_array) platform_tag_confines_array ||= [] @tag_confine_details_hash = {} platform_tag_confines_array.each do |entry| entry[:tag_reason_hash].keys.each do |tag| @tag_confine_details_hash[tag] ||= [] log_msg = "Tag '#{tag}' found, confining: except platforms " log_msg << "matching regex '#{entry[:platform]}'. Reason: " log_msg << "'#{entry[:tag_reason_hash][tag]}'" @tag_confine_details_hash[tag] << { :platform_regex => entry[:platform], :log_message => log_msg, :type => :except } end end end # Gets the confine details needed for a set of tags # # @param [Array] tags Tags of the given test # # @return [ArrayObject}>] an array of # Confine details hashes, which are hashes of symbols # to their properties, which are objects of various # kinds, depending on the key def confine_details(tags) tags ||= [] details = [] tags.each do |tag| tag_confine_array = @tag_confine_details_hash[tag] next if tag_confine_array.nil? details.push( *tag_confine_array ) # tag_confine_array.each do |confine_details| # details << confine_details # end end details end end end end endbeaker-4.30.0/lib/beaker/dsl/wrappers.rb000066400000000000000000000057231407603575700200250ustar00rootroot00000000000000require 'base64' module Beaker module DSL # These are wrappers to equivalent {Beaker::Command} objects # so that command line actions are executed within an appropriate and # configurable environment. # # I find most of these adapters of suspicious value and have deprecated # many of them. module Wrappers # @param [String] command_string A string of to be interpolated # within the context of a host in # question # @example Usage # @!visibility private def host_command(command_string) HostCommand.new(command_string) end # Returns a {Beaker::Command} object for executing powershell commands on a host # # @param [String] command The powershell command to execute # @param [Hash] args The commandline parameters to be passed to powershell # # @example Setting the contents of a file # powershell("Set-Content -path 'fu.txt' -value 'fu'") # # @example Using an alternative execution policy # powershell("Set-Content -path 'fu.txt' -value 'fu'", {'ExecutionPolicy' => 'Unrestricted'}) # # @example Using an EncodedCommand (defaults to non-encoded) # powershell("Set Content -path 'fu.txt', -value 'fu'", {'EncodedCommand => true}) # # @example executing from a file # powershell("", {'-File' => '/path/to/file'}) # # @return [Command] def powershell(command, args={}) ps_opts = { 'ExecutionPolicy' => 'Bypass', 'InputFormat' => 'None', 'NoLogo' => '', 'NoProfile' => '', 'NonInteractive' => '' } encoded = false ps_opts.merge!(args) ps_args = [] # determine if the command should be encoded if ps_opts.has_key?('EncodedCommand') v = ps_opts.delete('EncodedCommand') # encode the commend if v is true, nil or empty encoded = v || v.eql?('') || v.nil? end ps_opts.each do |k, v| if v.eql?('') or v.nil? ps_args << "-#{k}" else ps_args << "-#{k} #{v}" end end # may not have a command if executing a file if command && !command.empty? if encoded ps_args << "-EncodedCommand #{encode_command(command)}" else ps_args << "-Command #{command}" end end Command.new("powershell.exe", ps_args) end # Convert the provided command string to Base64 encoded UTF-16LE command # @param [String] cmd The command to convert to Base64 # @return [String] The converted string # @api private def encode_command(cmd) # use strict_encode because linefeeds are not correctly handled in our model Base64.strict_encode64(cmd.encode(Encoding::UTF_16LE)).chomp end end end end beaker-4.30.0/lib/beaker/host.rb000066400000000000000000000526531407603575700163610ustar00rootroot00000000000000require 'socket' require 'timeout' require 'benchmark' require 'rsync' require 'beaker/dsl/helpers' require 'beaker/dsl/patterns' [ 'command', 'ssh_connection', 'local_connection' ].each do |lib| require "beaker/#{lib}" end module Beaker class Host SELECT_TIMEOUT = 30 include Beaker::DSL::Helpers include Beaker::DSL::Patterns class CommandFailure < StandardError; end class RebootFailure < CommandFailure; end class RebootWarning < StandardError; end # This class provides array syntax for using puppet --configprint on a host class PuppetConfigReader def initialize(host, command) @host = host @command = command end def has_key?(k) cmd = PuppetCommand.new(@command, '--configprint all') keys = @host.exec(cmd).stdout.split("\n").collect do |x| x[/^[^\s]+/] end keys.include?(k) end def [](k) cmd = PuppetCommand.new(@command, "--configprint #{k.to_s}") @host.exec(cmd).stdout.strip end end def self.create name, host_hash, options case host_hash['platform'] when /windows/ cygwin = host_hash['is_cygwin'] if cygwin.nil? or cygwin == true Windows::Host.new name, host_hash, options else PSWindows::Host.new name, host_hash, options end when /aix/ Aix::Host.new name, host_hash, options when /osx/ Mac::Host.new name, host_hash, options when /freebsd/ FreeBSD::Host.new name, host_hash, options when /eos/ Eos::Host.new name, host_hash, options when /cisco/ Cisco::Host.new name, host_hash, options else Unix::Host.new name, host_hash, options end end attr_accessor :logger attr_reader :name, :host_hash, :options def initialize name, host_hash, options @logger = host_hash[:logger] || options[:logger] @name, @host_hash, @options = name.to_s, host_hash.dup, options.dup @host_hash['packaging_platform'] ||= @host_hash['platform'] @host_hash = self.platform_defaults.merge(@host_hash) pkg_initialize end def pkg_initialize # This method should be overridden by platform-specific code to # handle whatever packaging-related initialization is necessary. end def node_name # TODO: might want to consider caching here; not doing it for now because # I haven't thought through all of the possible scenarios that could # cause the value to change after it had been cached. result = puppet_configprint['node_name_value'].strip end def port_open? port begin Timeout.timeout SELECT_TIMEOUT do TCPSocket.new(reachable_name, port).close return true end rescue Errno::ECONNREFUSED, Timeout::Error, Errno::ETIMEDOUT, Errno::EHOSTUNREACH return false end end # Wait for a port on the host. Useful for those occasions when you've called # host.reboot and want to avoid spam from subsequent SSH connections retrying # to connect from say retry_on() def wait_for_port(port, attempts=15) @logger.debug(" Waiting for port #{port} ... ", false) start = Time.now done = repeat_fibonacci_style_for(attempts) { port_open?(port) } if done @logger.debug('connected in %0.2f seconds' % (Time.now - start)) else @logger.debug('timeout') end done end def up? begin Socket.getaddrinfo( reachable_name, nil ) return true rescue SocketError return false end end # Return the preferred method to reach the host, will use IP is available and then default to {#hostname}. def reachable_name self['ip'] || hostname end # Returning our PuppetConfigReader here allows users of the Host # class to do things like `host.puppet['vardir']` to query the # 'main' section or, if they want the configuration for a # particular run type, `host.puppet('agent')['vardir']` def puppet_configprint(command='agent') PuppetConfigReader.new(self, command) end alias_method :puppet, :puppet_configprint def []= k, v host_hash[k] = v end # Does this host have this key? Either as defined in the host itself, or globally? def [] k host_hash[k] || options[k] end # Does this host have this key? Either as defined in the host itself, or globally? def has_key? k host_hash.has_key?(k) || options.has_key?(k) end def delete k host_hash.delete(k) end # The {#hostname} of this host. def to_str hostname end # The {#hostname} of this host. def to_s hostname end # Return the public name of the particular host, which may be different then the name of the host provided in # the configuration file as some provisioners create random, unique hostnames. def hostname host_hash['vmhostname'] || @name end def + other @name + other end def is_pe? self['type'] && self['type'].to_s =~ /pe/ end def is_cygwin? self.class == Windows::Host end def is_powershell? self.class == PSWindows::Host end def platform self['platform'] end # True if this is a pe run, or if the host has had a 'use-service' property set. def use_service_scripts? is_pe? || self['use-service'] end # Mirrors the true/false value of the host's 'graceful-restarts' property, # or falls back to the value of +is_using_passenger?+ if # 'graceful-restarts' is nil, but only if this is not a PE run (foss only). def graceful_restarts? graceful = if !self['graceful-restarts'].nil? self['graceful-restarts'] else !is_pe? && is_using_passenger? end graceful end # Returns true if the host is running in FIPS mode. # # We currently only test FIPS mode on Redhat 7. Other detection # modes should be added here if we expand FIPS support to other # platforms. def fips_mode? case self['platform'] when /el-7/ begin execute("cat /proc/sys/crypto/fips_enabled") == "1" rescue Beaker::Host::CommandFailure false end else false end end # Modifies the host settings to indicate that it will be using passenger service scripts, # (apache2) by default. Does nothing if this is a PE host, since it is already using # passenger. # @param [String] puppetservice Name of the service script that should be # called to stop/startPuppet on this host. Defaults to 'apache2'. def uses_passenger!(puppetservice = 'apache2') if !is_pe? self['passenger'] = true self['puppetservice'] = puppetservice self['use-service'] = true end return true end # True if this is a PE run, or if the host's 'passenger' property has been set. def is_using_passenger? is_pe? || self['passenger'] end def log_prefix if host_hash['vmhostname'] "#{self} (#{@name})" else self.to_s end end #Determine the ip address of this host def get_ip @logger.warn("Uh oh, this should be handled by sub-classes but hasn't been") end # Determine the ip address using logic specific to the hypervisor def get_public_ip case host_hash[:hypervisor] when /^(ec2|openstack)$/ if self[:hypervisor] == 'ec2' && self[:instance] return self[:instance].ip_address elsif self[:hypervisor] == 'openstack' && self[:ip] return self[:ip] else # In the case of using ec2 instances with the --no-provision flag, the ec2 # instance object does not exist and we should just use the curl endpoint # specified here: # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html if self.instance_of?(Windows::Host) execute("wget http://169.254.169.254/latest/meta-data/public-ipv4").strip else execute("curl http://169.254.169.254/latest/meta-data/public-ipv4").strip end end end end #Return the ip address of this host #Always pull fresh, because this can sometimes change def ip self['ip'] = get_public_ip || get_ip end #@return [Boolean] true if x86_64, false otherwise def is_x86_64? @x86_64 ||= determine_if_x86_64 end def connection # create new connection object if necessary if self['hypervisor'] == 'none' && @name == 'localhost' @connection ||= LocalConnection.connect( { :ssh_env_file => self['ssh_env_file'], :logger => @logger }) return @connection end @connection ||= SshConnection.connect( { :ip => self['ip'], :vmhostname => self['vmhostname'], :hostname => @name }, self['user'], self['ssh'], { :logger => @logger, :ssh_connection_preference => self[:ssh_connection_preference]} ) # update connection information if self['ip'] && (@connection.ip != self['ip']) @connection.ip = self['ip'] end if self['vmhostname'] && (@connection.vmhostname != self['vmhostname']) @connection.vmhostname = self['vmhostname'] end if @name && (@connection.hostname != @name) @connection.hostname = @name end @connection end def close if @connection @connection.close # update connection information @connection.ip = self['ip'] if self['ip'] @connection.vmhostname = self['vmhostname'] if self['vmhostname'] @connection.hostname = @name end @connection = nil end def exec command, options={} result = nil # I've always found this confusing cmdline = command.cmd_line(self) # use the value of :dry_run passed to the method unless # undefined, then use parsed @options hash. options[:dry_run] ||= @options[:dry_run] if options[:dry_run] @logger.debug "\n Running in :dry_run mode. Command #{cmdline} not executed." result = Beaker::NullResult.new(self, command) return result end if options[:silent] output_callback = nil else @logger.debug "\n#{log_prefix} #{Time.new.strftime('%H:%M:%S')}$ #{cmdline}" if @options[:color_host_output] output_callback = logger.method(:color_host_output) else output_callback = logger.method(:host_output) end end unless options[:dry_run] # is this returning a result object? # the options should come at the end of the method signature (rubyism) # and they shouldn't be ssh specific seconds = Benchmark.realtime { @logger.with_indent do result = connection.execute(cmdline, options, output_callback) end } if not options[:silent] @logger.debug "\n#{log_prefix} executed in %0.2f seconds" % seconds end if options[:reset_connection] # Expect the connection to fail hard and possibly take a long time timeout. # Pre-emptively reset it so we don't wait forever. close return result end unless options[:silent] # What? result.log(@logger) if !options[:expect_connection_failure] && !result.exit_code # no exit code was collected, so the stream failed raise CommandFailure, "Host '#{self}' connection failure running:\n #{cmdline}\nLast #{@options[:trace_limit]} lines of output were:\n#{result.formatted_output(@options[:trace_limit])}" end if options[:expect_connection_failure] && result.exit_code # should have had a connection failure, but didn't # wait to see if the connection failure will be generation, otherwise raise error if not connection.wait_for_connection_failure(options, output_callback) raise CommandFailure, "Host '#{self}' should have resulted in a connection failure running:\n #{cmdline}\nLast #{@options[:trace_limit]} lines of output were:\n#{result.formatted_output(@options[:trace_limit])}" end end # No, TestCase has the knowledge about whether its failed, checking acceptable # exit codes at the host level and then raising... # is it necessary to break execution?? if options[:accept_all_exit_codes] && options[:acceptable_exit_codes] @logger.warn ":accept_all_exit_codes & :acceptable_exit_codes set. :acceptable_exit_codes overrides, but they shouldn't both be set at once" options[:accept_all_exit_codes] = false end if !options[:accept_all_exit_codes] && !result.exit_code_in?(Array(options[:acceptable_exit_codes] || [0, nil])) raise CommandFailure, "Host '#{self}' exited with #{result.exit_code} running:\n #{cmdline}\nLast #{@options[:trace_limit]} lines of output were:\n#{result.formatted_output(@options[:trace_limit])}" end end end result end # scp files from the localhost to this test host, if a directory is provided it is recursively copied. # If the provided source is a directory both the contents of the directory and the directory # itself will be copied to the host, if you only want to copy directory contents you will either need to specify # the contents file by file or do a separate 'mv' command post scp_to to create the directory structure as desired. # To determine if a file/dir is 'ignored' we compare to any contents of the source dir and NOT any part of the path # to that source dir. # # @param source [String] The path to the file/dir to upload # @param target_path [String] The destination path on the host # @param options [Hash{Symbol=>String}] Options to alter execution # @option options [Array] :ignore An array of file/dir paths that will not be copied to the host # @example # do_scp_to('source/dir1/dir2/dir3', 'target') # -> will result in creation of target/source/dir1/dir2/dir3 on host # # do_scp_to('source/file.rb', 'target', { :ignore => 'file.rb' } # -> will result in not files copyed to the host, all are ignored def do_scp_to source, target_path, options target = self.scp_path( target_path ) # use the value of :dry_run passed to the method unless # undefined, then use parsed @options hash. options[:dry_run] ||= @options[:dry_run] if options[:dry_run] scp_cmd = "scp #{source} #{@name}:#{target}" @logger.debug "\n Running in :dry_run mode. localhost $ #{scp_cmd} not executed." return NullResult.new(self, scp_cmd) end @logger.notify "localhost $ scp #{source} #{@name}:#{target} {:ignore => #{options[:ignore]}}" result = Result.new(@name, [source, target]) has_ignore = options[:ignore] and not options[:ignore].empty? # construct the regex for matching ignored files/dirs ignore_re = nil if has_ignore ignore_arr = Array(options[:ignore]).map do |entry| "((\/|\\A)#{Regexp.escape(entry)}(\/|\\z))" end ignore_re = Regexp.new(ignore_arr.join('|')) @logger.debug("going to ignore #{ignore_re}") end # either a single file, or a directory with no ignores if not File.file?(source) and not File.directory?(source) raise IOError, "No such file or directory - #{source}" end if File.file?(source) or (File.directory?(source) and not has_ignore) source_file = source if has_ignore and (source =~ ignore_re) @logger.trace "After rejecting ignored files/dirs, there is no file to copy" source_file = nil result.stdout = "No files to copy" result.exit_code = 1 end if source_file result = connection.scp_to(source_file, target, options) @logger.trace result.stdout end else # a directory with ignores dir_source = Dir.glob("#{source}/**/*").reject do |f| f.gsub(/\A#{Regexp.escape(source)}/, '') =~ ignore_re #only match against subdirs, not full path end @logger.trace "After rejecting ignored files/dirs, going to scp [#{dir_source.join(", ")}]" # create necessary directory structure on host # run this quietly (no STDOUT) @logger.quiet(true) required_dirs = (dir_source.map{ | dir | File.dirname(dir) }).uniq require 'pathname' required_dirs.each do |dir| dir_path = Pathname.new(dir) if dir_path.absolute? and (File.dirname(File.absolute_path(source)).to_s != '/') mkdir_p(File.join(target, dir.gsub(/#{Regexp.escape(File.dirname(File.absolute_path(source)))}/, ''))) else mkdir_p( File.join(target, dir) ) end end @logger.quiet(false) # copy each file to the host dir_source.each do |s| # Copy files, not directories (as they are copied recursively) next if File.directory?(s) s_path = Pathname.new(s) if s_path.absolute? and (File.dirname(File.absolute_path(source)).to_s != '/') file_path = File.join(target, File.dirname(s).gsub(/#{Regexp.escape(File.dirname(File.absolute_path(source)))}/,'')) else file_path = File.join(target, File.dirname(s)) end result = connection.scp_to(s, file_path, options) @logger.trace result.stdout end end self.scp_post_operations( target, target_path ) return result end def do_scp_from source, target, options # use the value of :dry_run passed to the method unless # undefined, then use parsed @options hash. options[:dry_run] ||= @options[:dry_run] if options[:dry_run] scp_cmd = "scp #{@name}:#{source} #{target}" @logger.debug "\n Running in :dry_run mode. localhost $ #{scp_cmd} not executed." return NullResult.new(self, scp_cmd) end @logger.debug "localhost $ scp #{@name}:#{source} #{target}" result = connection.scp_from(source, target, options) @logger.debug result.stdout return result end # rsync a file or directory from the localhost to this test host # @param from_path [String] The path to the file/dir to upload # @param to_path [String] The destination path on the host # @param opts [Hash{Symbol=>String}] Options to alter execution # @option opts [Array] :ignore An array of file/dir paths that will not be copied to the host # @raise [Beaker::Host::CommandFailure] With Rsync error (if available) # @return [Rsync::Result] Rsync result with status code def do_rsync_to from_path, to_path, opts = {} ssh_opts = self['ssh'] rsync_args = [] ssh_args = [] if not File.file?(from_path) and not File.directory?(from_path) raise IOError, "No such file or directory - #{from_path}" end # We enable achieve mode and compression rsync_args << "-az" if not self['user'] user = "root" else user = self['user'] end hostname_with_user = "#{user}@#{reachable_name}" Rsync.host = hostname_with_user # vagrant uses temporary ssh configs in order to use dynamic keys # without this config option using ssh may prompt for password # # We still want any user-set SSH config to win though filesystem_ssh_config = nil if ssh_opts[:config] && File.exists?(ssh_opts[:config]) filesystem_ssh_config = ssh_opts[:config] elsif self[:vagrant_ssh_config] && File.exists?(self[:vagrant_ssh_config]) filesystem_ssh_config = self[:vagrant_ssh_config] end if filesystem_ssh_config ssh_args << "-F #{filesystem_ssh_config}" else if ssh_opts.has_key?('keys') and ssh_opts.has_key?('auth_methods') and ssh_opts['auth_methods'].include?('publickey') # find the first SSH key that exists key = Array(ssh_opts['keys']).find do |k| File.exist?(k) end if key # rsync doesn't always play nice with tilde, so be sure to expand first ssh_args << "-i #{File.expand_path(key)}" end end end if ssh_opts.has_key?(:port) ssh_args << "-p #{ssh_opts[:port]}" end # We disable prompt when host isn't known ssh_args << "-o 'StrictHostKeyChecking no'" if not ssh_args.empty? rsync_args << "-e \"ssh #{ssh_args.join(' ')}\"" end if opts.has_key?(:ignore) and not opts[:ignore].empty? rsync_args << opts[:ignore].map { |value| "--exclude '#{value}'" }.join(' ') end # We assume that the *contents* of the directory 'from_path' needs to be # copied into the directory 'to_path' if File.directory?(from_path) and not from_path.end_with?('/') from_path += '/' end @logger.notify "rsync: localhost:#{from_path} to #{hostname_with_user}:#{to_path} {:ignore => #{opts[:ignore]}}" result = Rsync.run(from_path, to_path, rsync_args) @logger.debug("rsync returned #{result.inspect}") return result if result.success? raise Beaker::Host::CommandFailure, result.error end end [ 'unix', 'aix', 'mac', 'freebsd', 'windows', 'pswindows', 'eos', 'cisco', ].each do |lib| require "beaker/host/#{lib}" end end beaker-4.30.0/lib/beaker/host/000077500000000000000000000000001407603575700160215ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/aix.rb000066400000000000000000000004741407603575700171340ustar00rootroot00000000000000[ 'host', 'command_factory' ].each do |lib| require "beaker/#{lib}" end module Aix class Host < Unix::Host [ 'user', 'group', 'file', 'exec' ].each do |lib| require "beaker/host/aix/#{lib}" end include Aix::User include Aix::Group include Aix::File include Aix::Exec end end beaker-4.30.0/lib/beaker/host/aix/000077500000000000000000000000001407603575700166025ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/aix/exec.rb000066400000000000000000000015051407603575700200540ustar00rootroot00000000000000module Aix::Exec include Beaker::CommandFactory def reboot exec(Beaker::Command.new("shutdown -Fr"), :expect_connection_failure => true) end def get_ip execute("ifconfig -a inet| awk '/broadcast/ {print $2}' | cut -d/ -f1 | head -1").strip end # Restarts the SSH service # # @return [Result] result of starting ssh service def ssh_service_restart exec(Beaker::Command.new("stopsrc -g ssh")) exec(Beaker::Command.new("startsrc -g ssh")) end # Sets the PermitUserEnvironent setting & restarts the SSH service # # @api private # @return [Result] result of the command starting the SSH service # (from {#ssh_service_restart}). def ssh_permit_user_environment exec(Beaker::Command.new("echo '\nPermitUserEnvironment yes' >> /etc/ssh/sshd_config")) ssh_service_restart() end end beaker-4.30.0/lib/beaker/host/aix/file.rb000066400000000000000000000005501407603575700200460ustar00rootroot00000000000000module Aix::File include Beaker::CommandFactory def tmpfile(name = '') execute("rndnum=${RANDOM} && touch /tmp/#{name}.${rndnum} && echo /tmp/#{name}.${rndnum}") end def tmpdir(name = '') execute("rndnum=${RANDOM} && mkdir /tmp/#{name}.${rndnum} && echo /tmp/#{name}.${rndnum}") end def path_split(paths) paths.split(':') end end beaker-4.30.0/lib/beaker/host/aix/group.rb000066400000000000000000000014741407603575700202710ustar00rootroot00000000000000module Aix::Group include Beaker::CommandFactory def group_list(&block) execute("lsgroup -a ALL") do |result| yield result if block_given? result.stdout.lines.map(&:strip) end end def group_get(name, &block) execute("lsgroup #{name}") do |result| fail_test "failed to get group #{name}" unless result.stdout =~ /^#{name} id/ yield result if block_given? result end end def group_gid(name) execute("lsgroup -a id #{name}") do |result| # Format is: # staff id=500 result.stdout.split('=').last.strip end end def group_present(name, &block) execute("if ! lsgroup #{name}; then mkgroup #{name}; fi", {}, &block) end def group_absent(name, &block) execute("if lsgroup #{name}; then rmgroup #{name}; fi", {}, &block) end end beaker-4.30.0/lib/beaker/host/aix/user.rb000066400000000000000000000013231407603575700201040ustar00rootroot00000000000000module Aix::User include Beaker::CommandFactory def user_list(&block) execute("lsuser ALL") do |result| users = [] result.stdout.each_line do |line| users << line.split(' ')[0] end yield result if block_given? users end end def user_get(name, &block) execute("lsuser #{name}") do |result| fail_test "failed to get user #{name}" unless result.stdout =~ /^#{name} id/ yield result if block_given? result end end def user_present(name, &block) execute("if ! lsuser #{name}; then mkuser #{name}; fi", {}, &block) end def user_absent(name, &block) execute("if lsuser #{name}; then rmuser #{name}; fi", {}, &block) end end beaker-4.30.0/lib/beaker/host/cisco.rb000066400000000000000000000125341407603575700174530ustar00rootroot00000000000000[ 'host', 'command_factory' ].each do |lib| require "beaker/#{lib}" end module Cisco class Host < Unix::Host # as the cisco hosts tend to have custom # ssh configuration, the presets # do not apply where verification of the # host keys is disabled def platform_defaults h = Beaker::Options::OptionsHash.new h.merge({ 'ssh' => { :verify_host_key => false, }, }) end # Tells you whether a host platform supports beaker's # {Beaker::HostPrebuiltSteps#set_env} method # # @return [String,nil] Reason message if set_env should be skipped, # nil if it should run. def skip_set_env? 'Cisco does not allow SSH control through the BASH shell' end # Handles host operations needed after an SCP takes place # # @param [String] scp_file_actual File path to actual SCP'd file on host # @param [String] scp_file_target File path to target SCP location on host # # @return nil def scp_post_operations(scp_file_actual, scp_file_target) if self[:platform] =~ /cisco_nexus/ execute( "mv #{scp_file_actual} #{scp_file_target}" ) end nil end # Handles any changes needed in a path for SCP # # @param [String] path File path to SCP to # # @return [String] path, changed if needed due to host # constraints def scp_path(path) if self[:platform] =~ /cisco_nexus/ @home_dir ||= execute( 'pwd' ) answer = "#{@home_dir}/#{File.basename( path )}" answer << '/' if path =~ /\/$/ return answer end path end # Gets the repo type for the given platform # # @raise [ArgumentError] For an unknown platform # # @return [String] Type of repo (rpm|deb) def repo_type 'rpm' end # Gets the config dir location for package information # # @raise [ArgumentError] For an unknown platform # # @return [String] Path to package config dir def package_config_dir '/etc/yum/repos.d/' end # Gets the specific prepend commands as needed for this host # # @param [String] command Command to be executed # @param [String] user_pc List of user-specified commands to prepend # @param [Hash] opts optional parameters # # @return [String] Command string as needed for this host def prepend_commands(command = '', user_pc = '', opts = {}) return user_pc unless command.index('vsh').nil? if self[:platform] =~ /cisco_nexus/ return user_pc unless command.index('ntpdate').nil? end prepend_cmds = 'source /etc/profile;' prepend_cmds << " sudo -E sh -c \"" if self[:user] != 'root' if self[:vrf] prepend_cmds << "ip netns exec #{self[:vrf]} " end if user_pc && !user_pc.empty? prepend_cmds << "#{user_pc} " end prepend_cmds.strip end # Gets the specific append commands as needed for this host # # @param [String] command Command to be executed # @param [String] user_ac List of user-specified commands to append # @param [Hash] opts optional parameters # # @return [String] Command string as needed for this host def append_commands(command = '', user_ac = '', opts = {}) command.gsub('"') {'\\"'} # vsh commands, ntpdate or when user is root commands do not require an appended `"` return '"' unless command =~ /ntpdate|\/isan\/bin\/vsh/ || self[:user] == 'root' end # Construct the environment string for this command # # @param [Hash{String=>String}] env An optional Hash containing # key-value pairs to be treated # as environment variables that # should be set for the duration # of the puppet command. # # @return [String] Returns a string containing command line arguments that # will ensure the environment is correctly set for the # given host. def environment_string env prestring = '' return prestring if env.empty? env_array = self.environment_variable_string_pair_array( env ) environment_string = env_array.join(' ') if self[:platform] =~ /cisco_nexus/ prestring << " export" else prestring << " env" end environment_string = "#{prestring} #{environment_string}" environment_string << ';' if prestring =~ /export/ environment_string end # Validates that the host was setup correctly # # @return nil # @raise [ArgumentError] If the host is setup incorrectly, # this will be raised with the appropriate message def validate_setup msg = nil if self[:platform] =~ /cisco_nexus/ if !self[:vrf] msg = 'Cisco Nexus hosts must be provided with a :vrf value.' end if !self[:user] msg = 'Cisco hosts must be provided with a :user value' end end if self[:platform] =~ /cisco_ios_xr/ if !self[:user] msg = 'Cisco hosts must be provided with a :user value' end end if msg msg << <<-EOF Check https://github.com/puppetlabs/beaker/blob/master/docs/hosts/cisco.md for more info.' EOF raise ArgumentError, msg end end end end beaker-4.30.0/lib/beaker/host/eos.rb000066400000000000000000000050011407603575700171300ustar00rootroot00000000000000[ 'host', 'command_factory' ].each do |lib| require "beaker/#{lib}" end module Eos class Host < Unix::Host # Gets the path & file name for the puppet agent dev package on EOS # # @param [String] puppet_collection Name of the puppet collection to use # @param [String] puppet_agent_version Version of puppet agent to get # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @note EOS doesn't use any additional options at this time, but does require # both puppet_collection & puppet_agent_version, & will fail without them # # @raise [ArgumentError] If one of the two required parameters (puppet_collection, # puppet_agent_version) is either not passed or set to nil # # @return [String, String] Path to the directory and filename of the package, respectively def puppet_agent_dev_package_info( puppet_collection = nil, puppet_agent_version = nil, opts = {} ) error_message = "Must provide %s argument to get puppet agent dev package information" raise ArgumentError, error_message % "puppet_collection" unless puppet_collection raise ArgumentError, error_message % "puppet_agent_version" unless puppet_agent_version variant, version, arch, _ = self['platform'].to_array release_path = "#{variant}/#{version}/#{puppet_collection}/#{arch}" release_file = "puppet-agent-#{puppet_agent_version}-1.#{variant}#{version}.#{arch}.swix" return release_path, release_file end # Copies a remote file to the host location specified # # @param [String] remote_url URL to the remote file # @param [String] host_directory Path to the host directory on the host. # # @note in EOS, you just copy the file as an extension, you don't worry # about location, so that parameter is ignored # # @return [Result] The result of copying that file to the host def get_remote_file( remote_url, host_directory = '' ) commands = ['enable', "copy #{remote_url} extension:"] command = commands.join("\n") execute("Cli -c '#{command}'") end # Installs an extension file already copied via {#get_remote_file} or something similar # # @param [String] filename Name of the file to install, including file extension # # @return [Result] The result of running the install command on the host def install_from_file( filename ) commands = ['enable', "extension #{filename}"] command = commands.join("\n") execute("Cli -c '#{command}'") end end endbeaker-4.30.0/lib/beaker/host/freebsd.rb000066400000000000000000000007721407603575700177660ustar00rootroot00000000000000[ 'host', 'command_factory' ].each do |lib| require "beaker/#{lib}" end module FreeBSD class Host < Unix::Host [ 'exec', 'pkg', ].each do |lib| require "beaker/host/freebsd/#{lib}" end include FreeBSD::Exec include FreeBSD::Pkg def platform_defaults h = Beaker::Options::OptionsHash.new h.merge({ 'user' => 'root', 'group' => 'root', 'pathseparator' => ':', }) end end end beaker-4.30.0/lib/beaker/host/freebsd/000077500000000000000000000000001407603575700174335ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/freebsd/exec.rb000066400000000000000000000004701407603575700207050ustar00rootroot00000000000000module FreeBSD::Exec include Beaker::CommandFactory def echo_to_file(str, filename) # FreeBSD gets weird about special characters, we have to go a little OTT here escaped_str = str.gsub(/\t/,'\\t').gsub(/\n/,'\\n') exec(Beaker::Command.new("printf \"#{escaped_str}\" > #{filename}")) end end beaker-4.30.0/lib/beaker/host/freebsd/pkg.rb000066400000000000000000000026171407603575700205470ustar00rootroot00000000000000module FreeBSD::Pkg include Beaker::CommandFactory def pkg_info_pattern(package) # This seemingly restrictive pattern prevents false positives... "^#{package}-[0-9][0-9a-zA-Z_\\.,]*$" end def check_pkgng_sh 'TMPDIR=/dev/null ASSUME_ALWAYS_YES=1 PACKAGESITE=file:///nonexist ' \ 'pkg info -x "pkg(-devel)?\\$" > /dev/null 2>&1' end def pkgng_active?(opts = {}) opts = {:accept_all_exit_codes => true}.merge(opts) execute("/bin/sh -c '#{check_pkgng_sh}'", opts) { |r| r }.exit_code == 0 end def install_package(package, cmdline_args = nil, opts = {}) cmd = if pkgng_active? "pkg install #{cmdline_args || '-y'} #{package}" else "pkg_add #{cmdline_args || '-r'} #{package}" end execute(cmd, opts) { |result| result } end def uninstall_package(package, cmdline_args = nil, opts = {}) cmd = if pkgng_active? "pkg delete #{cmdline_args || '-y'} #{package}" else "pkg_delete #{cmdline_args || '-r'} #{package}" end execute(cmd, opts) { |result| result } end def check_for_package(package, opts = {}) opts = {:accept_all_exit_codes => true}.merge(opts) cmd = if pkgng_active? "pkg info #{package}" else "pkg_info -Ix '#{pkg_info_pattern(package)}'" end execute(cmd, opts) { |result| result }.exit_code == 0 end end beaker-4.30.0/lib/beaker/host/mac.rb000066400000000000000000000012651407603575700171120ustar00rootroot00000000000000[ 'host', 'command_factory', 'command', 'options' ].each do |lib| require "beaker/#{lib}" end module Mac class Host < Unix::Host [ 'exec', 'user', 'group', 'pkg' ].each do |lib| require "beaker/host/mac/#{lib}" end include Mac::Exec include Mac::User include Mac::Group include Mac::Pkg def platform_defaults h = Beaker::Options::OptionsHash.new h.merge({ 'user' => 'root', 'group' => 'root', 'pathseparator' => ':', }) end attr_reader :external_copy_base def initialize name, host_hash, options super @external_copy_base = '/var/root' end end end beaker-4.30.0/lib/beaker/host/mac/000077500000000000000000000000001407603575700165615ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/mac/exec.rb000066400000000000000000000026451407603575700200410ustar00rootroot00000000000000module Mac::Exec include Beaker::CommandFactory def touch(file, abs=true) (abs ? '/usr/bin/touch' : 'touch') + " #{file}" end # Restarts the SSH service # # @return [Result] result of starting SSH service def ssh_service_restart launch_daemons_plist = '/System/Library/LaunchDaemons/ssh.plist' exec(Beaker::Command.new("launchctl unload #{launch_daemons_plist}")) exec(Beaker::Command.new("launchctl load #{launch_daemons_plist}")) end # Sets the PermitUserEnvironment setting & restarts the SSH service # # @api private # @return [Result] result of the command starting the SSH service # (from {#ssh_service_restart}) def ssh_permit_user_environment ssh_config_file = '/etc/sshd_config' ssh_config_file = '/private/etc/ssh/sshd_config' if self['platform'] =~ /^osx-/ exec(Beaker::Command.new("echo '\nPermitUserEnvironment yes' >> #{ssh_config_file}")) ssh_service_restart() end # Checks if selinux is enabled # selinux is not availble on OS X # # @return [Boolean] false def selinux_enabled?() false end # Update ModifiedDate on a file # @param [String] file Path to the file # @param [String] timestamp Timestamp to set def modified_at(file, timestamp = nil) require 'date' time = timestamp ? DateTime.parse("#{timestamp}") : DateTime.now timestamp = time.strftime('%Y%m%d%H%M') execute("touch -mt #{timestamp} #{file}") end end beaker-4.30.0/lib/beaker/host/mac/group.rb000066400000000000000000000055561407603575700202550ustar00rootroot00000000000000module Mac::Group include Beaker::CommandFactory # Gets a list of group names on the system # # @param [Proc] block Additional actions or insertions # # @return [Array] The list of group names on the system def group_list(&block) execute('dscacheutil -q group') do |result| groups = [] result.stdout.each_line do |line| groups << line.split(': ')[1].strip if line =~ /^name:/ end yield result if block_given? groups end end # Gets the group information in /etc/group format # # @param [String] name Name of the group you want # @param [Proc] block Additional actions or insertions # # @yield [String] The actual mac dscacheutil output # @return [String] Group information in /etc/group format # @raise [FailTest] Raises an Assertion failure if it can't find the name # queried for in the returned block def group_get(name, &block) execute("dscacheutil -q group -a name #{name}") do |result| fail_test "failed to get group #{name}" unless result.stdout =~ /^name: #{name}/ gi = Hash.new # group info result.stdout.each_line { |line| pieces = line.split(': ') gi[pieces[0].to_sym] = pieces[1].strip if pieces[1] != nil } answer = "#{gi[:name]}:#{gi[:password]}:#{gi[:gid]}" yield answer if block_given? answer end end # Gets the gid of the given group # # @param [String] name Name of the group # # @return [String] gid of the group def group_gid(name) gid = -1 execute("dscacheutil -q group -a name #{name}") do |result| result.stdout.each_line { |line| if line =~ /^gid:/ gid = (line[5, line.length - 5]).chomp break end } gid end end # Makes sure the group is present, creating it if necessary # # @param [String] name Name of the group # @param [Proc] block Additional actions or insertions def group_present(name, &block) group_exists = false execute("dscacheutil -q group -a name #{name}") do |result| group_exists = result.stdout =~ /^name: #{name}/ end return if group_exists gid = gid_next create_cmd = "dscl . create /Groups/#{name}" create_cmd << " && dscl . create /Groups/#{name} PrimaryGroupID #{gid}" execute(create_cmd) end # Makes sure the group is absent, deleting it if necessary # # @param [String] name Name of the group # @param [Proc] block Additional actions or insertions def group_absent(name, &block) execute("if dscl . -list /Groups/#{name}; then dscl . -delete /Groups/#{name}; fi", {}, &block) end # Gives the next gid not used on the system # # @return [Fixnum] The next gid not used on the system def gid_next gid_last = execute("dscl . -list /Groups PrimaryGroupID | sort -k 2 -g | tail -1 | awk '{print $2}'") gid_last.to_i + 1 end end beaker-4.30.0/lib/beaker/host/mac/pkg.rb000066400000000000000000000150751407603575700176770ustar00rootroot00000000000000module Mac::Pkg include Beaker::CommandFactory def check_for_package(name) raise "Package #{name} cannot be queried on #{self}" end def install_package(name, cmdline_args = '', version = nil) generic_install_dmg("#{name}.dmg", name, "#{name}.pkg") end # Install a package from a specified dmg # # @param [String] dmg_file The dmg file, including path if not # relative. Can be a URL. # @param [String] pkg_base The base name of the directory that the dmg # attaches to under `/Volumes` # @param [String] pkg_name The name of the package file that should be # used by the installer # @example: Install vagrant from URL # mymachost.generic_install_dmg('https://releases.hashicorp.com/vagrant/1.8.4/vagrant_1.8.4.dmg', 'Vagrant', 'Vagrant.pkg') def generic_install_dmg(dmg_file, pkg_base, pkg_name) execute("test -f #{dmg_file}", :accept_all_exit_codes => true) do |result| execute("curl -O #{dmg_file}") unless result.exit_code == 0 end dmg_name = File.basename(dmg_file, '.dmg') execute("hdiutil attach #{dmg_name}.dmg") execute("installer -pkg /Volumes/#{pkg_base}/#{pkg_name} -target /") end def uninstall_package(name, cmdline_args = '') raise "Package #{name} cannot be uninstalled on #{self}" end # Upgrade an installed package to the latest available version # # @param [String] name The name of the package to update # @param [String] cmdline_args Additional command line arguments for # the package manager def upgrade_package(name, cmdline_args = '') raise "Package #{name} cannot be upgraded on #{self}" end # Deploy configuration generated by the packaging tooling to this host. # # This method calls one of #deploy_apt_repo, #deploy_yum_repo, or # #deploy_zyp_repo depending on the platform of this Host. # # @note See {Beaker::DSL::Helpers::HostHelpers#deploy_package_repo} for info on # params def deploy_package_repo(path, name, version) raise "Package repo cannot be deployed on #{self}; the platform is not supported" end #Examine the host system to determine the architecture #@return [Boolean] true if x86_64, false otherwise def determine_if_x86_64 result = exec(Beaker::Command.new("uname -a | grep x86_64"), :expect_all_exit_codes => true) result.exit_code == 0 end # Gets the path & file name for the puppet agent dev package on OSX # # @param [String] puppet_collection Name of the puppet collection to use # @param [String] puppet_agent_version Version of puppet agent to get # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @note OSX does require :download_url to be set on the opts argument # in order to check for builds on the builds server # # @raise [ArgumentError] If one of the two required parameters (puppet_collection, # puppet_agent_version) is either not passed or set to nil # # @return [String, String] Path to the directory and filename of the package, respectively def puppet_agent_dev_package_info( puppet_collection = nil, puppet_agent_version = nil, opts = {} ) error_message = "Must provide %s argument to get puppet agent dev package information" raise ArgumentError, error_message % "puppet_collection" unless puppet_collection raise ArgumentError, error_message % "puppet_agent_version" unless puppet_agent_version raise ArgumentError, error_message % "opts[:download_url]" unless opts[:download_url] variant, version, arch, codename = self['platform'].to_array mac_pkg_name = "puppet-agent-#{puppet_agent_version}" version = version[0,2] + '.' + version[2,2] unless version.include?(".") # newest hotness path_chunk = "apple/#{version}/#{puppet_collection}/#{arch}" release_path_end = path_chunk # moved to doing this when 'el capitan' came out & the objection was # raised that the code name wasn't a fact, & as such can be hard to script # example: puppet-agent-0.1.0-1.osx10.9.dmg release_file = "#{mac_pkg_name}-1.osx#{version}.dmg" if not link_exists?("#{opts[:download_url]}/#{release_path_end}/#{release_file}") # new hotness # little older change involved the code name as only difference from above # example: puppet-agent-0.1.0-1.mavericks.dmg release_file = "#{mac_pkg_name}-1.#{codename}.dmg" end if not link_exists?("#{opts[:download_url]}/#{release_path_end}/#{release_file}") # oops, try the old stuff release_path_end = "apple/#{puppet_collection}" # example: puppet-agent-0.1.0-osx-10.9-x86_64.dmg release_file = "#{mac_pkg_name}-#{variant}-#{version}-x86_64.dmg" end return release_path_end, release_file end # Gets host-specific information for PE promoted puppet-agent packages # # @param [String] puppet_collection Name of the puppet collection to use # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @return [String, String, String] Host-specific information for packages # 1. release_path_end Suffix for the release_path. Used on Windows. Check # {Windows::Pkg#pe_puppet_agent_promoted_package_info} to see usage. # 2. release_file Path to the file on release build servers # 3. download_file Filename for the package itself def pe_puppet_agent_promoted_package_info( puppet_collection = nil, opts = {} ) error_message = "Must provide %s argument to get puppet agent dev package information" raise ArgumentError, error_message % "puppet_collection" unless puppet_collection variant, version, arch, codename = self['platform'].to_array release_file = "/repos/apple/#{version}/#{puppet_collection}/#{arch}/puppet-agent-*" download_file = "puppet-agent-#{variant}-#{version}.tar.gz" return '', release_file, download_file end # Installs a given PE promoted package on a host # # @param [String] onhost_copy_base Base copy directory on the host # @param [String] onhost_copied_download Downloaded file path on the host # @param [String] onhost_copied_file Copied file path once un-compressed # @param [String] download_file File name of the downloaded file # @param [Hash{Symbol=>String}] opts additional options # # @return nil def pe_puppet_agent_promoted_package_install( onhost_copy_base, onhost_copied_download, onhost_copied_file, download_file, opts ) execute("tar -zxvf #{onhost_copied_download} -C #{onhost_copy_base}") # move to better location execute("mv #{onhost_copied_file}.dmg .") self.install_package("puppet-agent-*") end end beaker-4.30.0/lib/beaker/host/mac/user.rb000066400000000000000000000054021407603575700200650ustar00rootroot00000000000000module Mac::User include Beaker::CommandFactory # Gets a list of user names on the system # # @param [Proc] block Additional actions or insertions # # @return [Array] The list of user names on the system def user_list(&block) execute('dscacheutil -q user') do |result| users = [] result.stdout.each_line do |line| users << line.split(': ')[1].strip if line =~ /^name:/ end yield result if block_given? users end end # Gets the user information in /etc/passwd format # # @note Calls POSIX-compliant `$ id -P ` to get /etc/passwd-style # output # # @param [String] name Name of the user # @param [Proc] block Additional actions or insertions # # @yield [Result] User information in /etc/passwd format # @return [Result] User information in /etc/passwd format # @raise [FailTest] Raises an Assertion failure if it can't find the name # queried for in the returned block def user_get(name, &block) execute("id -P #{name}") do |result| fail_test "failed to get user #{name}" unless result.stdout =~ /^#{name}:/ yield result if block_given? result end end # Makes sure the user is present, creating them if necessary # # @param [String] name Name of the user # @param [Proc] block Additional actions or insertions def user_present(name, &block) user_exists = false execute("dscacheutil -q user -a name #{name}") do |result| user_exists = result.stdout =~ /^name: #{name}/ end return if user_exists uid = uid_next gid = gid_next create_cmd = "dscl . create /Users/#{name}" create_cmd << " && dscl . create /Users/#{name} NFSHomeDirectory /Users/#{name}" create_cmd << " && dscl . create /Users/#{name} UserShell /bin/bash" create_cmd << " && dscl . create /Users/#{name} UniqueID #{uid}" create_cmd << " && dscl . create /Users/#{name} PrimaryGroupID #{gid}" execute(create_cmd) end # Makes sure the user is absent, deleting them if necessary # # @param [String] name Name of the user # @param [Proc] block Additional actions or insertions def user_absent(name, &block) execute("if dscl . -list /Users/#{name}; then dscl . -delete /Users/#{name}; fi", {}, &block) end # Gives the next uid not used on the system # # @return [Fixnum] The next uid not used on the system def uid_next uid_last = execute("dscl . -list /Users UniqueID | sort -k 2 -g | tail -1 | awk '{print $2}'") uid_last.to_i + 1 end # Gives the next gid not used on the system # # @return [Fixnum] The next gid not used on the system def gid_next gid_last = execute("dscl . -list /Users PrimaryGroupID | sort -k 2 -g | tail -1 | awk '{print $2}'") gid_last.to_i + 1 end end beaker-4.30.0/lib/beaker/host/pswindows.rb000066400000000000000000000022071407603575700204040ustar00rootroot00000000000000[ 'host', 'command_factory', 'command', 'options', 'dsl/wrappers' ].each do |lib| require "beaker/#{lib}" end module PSWindows class Host < Windows::Host [ 'user', 'group', 'exec', 'pkg', 'file' ].each do |lib| require "beaker/host/pswindows/#{lib}" end include PSWindows::User include PSWindows::Group include PSWindows::File include PSWindows::Exec include PSWindows::Pkg def external_copy_base return @external_copy_base if @external_copy_base @external_copy_base = execute('for %I in (%ALLUSERSPROFILE%) do @echo %~I') @external_copy_base end # attr_reader :network_separator, :external_copy_base, :system_temp_path attr_reader :scp_separator, :system_temp_path def initialize name, host_hash, options super @scp_separator = '/' # %TEMP% == C:\Users\ADMINI~1\AppData\Local\Temp # is a user temp path, not the system path. Also, it doesn't work, there's # probably an issue with the `ADMINI~1` section @system_temp_path = 'C:\\Windows\\Temp' @external_copy_base = nil # @external_copy_base = '/programdata' end end end beaker-4.30.0/lib/beaker/host/pswindows/000077500000000000000000000000001407603575700200565ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/pswindows/exec.rb000066400000000000000000000176421407603575700213410ustar00rootroot00000000000000module PSWindows::Exec include Beaker::CommandFactory include Beaker::DSL::Wrappers def reboot exec(Beaker::Command.new("shutdown /r /t 0"), :expect_connection_failure => true) # rebooting on windows is slooooow sleep(40) end ABS_CMD = 'c:\\\\windows\\\\system32\\\\cmd.exe' CMD = 'cmd.exe' def echo(msg, abs=true) (abs ? ABS_CMD : CMD) + " /c echo #{msg}" end def touch(file, abs=true) (abs ? ABS_CMD : CMD) + " /c echo. 2> #{file}" end def rm_rf path # ensure that we have the right slashes for windows path = path.gsub(/\//, '\\') execute(%(del /s /q "#{path}")) end # Move the origin to destination. The destination is removed prior to moving. # @param [String] orig The origin path # @param [String] dest the destination path # @param [Boolean] rm Remove the destination prior to move def mv(orig, dest, rm=true) # ensure that we have the right slashes for windows orig = orig.gsub(/\//,'\\') dest = dest.gsub(/\//,'\\') rm_rf dest unless !rm execute("move /y #{orig} #{dest}") end # Update ModifiedDate on a file # @param [String] file Path to the file # @param [String] timestamp Timestamp to set def modified_at(file, timestamp = nil) require 'date' time = timestamp ? DateTime.parse("#{timestamp}") : DateTime.now result = execute("powershell Test-Path #{file} -PathType Leaf") if result.include? 'False' execute("powershell New-Item -ItemType file #{file}") end execute("powershell (gci #{file}).LastWriteTime = Get-Date " \ "-Year '#{time.year}'" \ "-Month '#{time.month}'" \ "-Day '#{time.day}'" \ "-Hour '#{time.hour}'" \ "-Minute '#{time.minute}'" \ "-Second '#{time.second}'" ) end def path 'c:/windows/system32;c:/windows' end def get_ip # when querying for an IP this way the return value can be formatted like: # IPAddress= # IPAddress={"129.168.0.1"} # IPAddress={"192.168.0.1","2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001"} ips = execute("wmic nicconfig where ipenabled=true GET IPAddress /format:list") ip = '' ips.each_line do |line| matches = line.split('=') next if matches.length <= 1 matches = matches[1].match(/^{"(.*?)"/) next if matches.nil? || matches.captures.nil? || matches.captures.empty? ip = matches.captures[0] if matches && matches.captures break if ip != '' end ip end # Attempt to ping the provided target hostname # @param [String] target The hostname to ping # @param [Integer] attempts Amount of times to attempt ping before giving up # @return [Boolean] true of ping successful, overwise false def ping target, attempts=5 try = 0 while try < attempts do result = exec(Beaker::Command.new("ping -n 1 #{target}"), :accept_all_exit_codes => true) if result.exit_code == 0 return true end try+=1 end result.exit_code == 0 end # Create the provided directory structure on the host # @param [String] dir The directory structure to create on the host # @return [Boolean] True, if directory construction succeeded, otherwise False def mkdir_p dir normalized_path = dir.gsub('/','\\') result = exec(powershell("New-Item -Path '#{normalized_path}' -ItemType 'directory'"), :acceptable_exit_codes => [0, 1]) result.exit_code == 0 end #Add the provided key/val to the current ssh environment #@param [String] key The key to add the value to #@param [String] val The value for the key #@example # host.add_env_var('PATH', '/usr/bin:PATH') def add_env_var key, val key = key.to_s.upcase #see if the key/value pair already exists cur_val = subbed_val = get_env_var(key, true) subbed_val = cur_val.gsub(/#{Regexp.escape(val.gsub(/'|"/, ''))}/, '') if cur_val.empty? exec(powershell("[Environment]::SetEnvironmentVariable('#{key}', '#{val}', 'Machine')")) self.close #refresh the state elsif subbed_val == cur_val #not present, add it exec(powershell("[Environment]::SetEnvironmentVariable('#{key}', '#{val};#{cur_val}', 'Machine')")) self.close #refresh the state end end #Delete the provided key/val from the current ssh environment #@param [String] key The key to delete the value from #@param [String] val The value to delete for the key #@example # host.delete_env_var('PATH', '/usr/bin:PATH') def delete_env_var key, val key = key.to_s.upcase #get the current value of the key cur_val = subbed_val = get_env_var(key, true) subbed_val = (cur_val.split(';') - [val.gsub(/'|"/, '')]).join(';') if subbed_val != cur_val #remove the current key value self.clear_env_var(key) #set to the truncated value self.add_env_var(key, subbed_val) end end #Return the value of a specific env var #@param [String] key The key to look for #@param [Boolean] clean Remove the 'KEY=' and only return the value of the env var #@example # host.get_env_var('path') def get_env_var key, clean = false self.close #refresh the state key = key.to_s.upcase val = exec(Beaker::Command.new("set #{key}"), :accept_all_exit_codes => true).stdout.chomp if val.empty? return '' else val = val.split(/\n/)[0] # only take the first result if clean val.gsub(/#{key}=/i,'') else val end end end #Delete the environment variable from the current ssh environment #@param [String] key The key to delete #@example # host.clear_env_var('PATH') def clear_env_var key key = key.to_s.upcase exec(powershell("[Environment]::SetEnvironmentVariable('#{key}', $null, 'Machine')")) exec(powershell("[Environment]::SetEnvironmentVariable('#{key}', $null, 'User')")) exec(powershell("[Environment]::SetEnvironmentVariable('#{key}', $null, 'Process')")) self.close #refresh the state end def environment_string env return '' if env.empty? env_array = self.environment_variable_string_pair_array( env ) environment_string = '' env_array.each_with_index do |env| environment_string += "set \"#{env}\" && " end environment_string end def environment_variable_string_pair_array env env_array = [] env.each_key do |key| val = env[key] if val.is_a?(Array) val = val.join(':') else val = val.to_s end # doing this for the key itself & the upcase'd version allows us to remain # backwards compatible # TODO: (Next Major Version) get rid of upcase'd version key_str = key.to_s keys = [key_str] keys << key_str.upcase if key_str.upcase != key_str keys.each do |env_key| env_array << "#{env_key}=#{val}" end end env_array end # Overrides the {Windows::Exec#ssh_permit_user_environment} method, # since no steps are needed in this setup to allow user ssh environments # to work. def ssh_permit_user_environment end # Sets the user SSH environment. # # @param [Hash{String=>String}] env Environment variables to set on the system, # in the form of a hash of String variable # names to their corresponding String values. # # @note this class doesn't manipulate an SSH environment file, it just sets # the environment variables on the system. # # @api private # @return nil def ssh_set_user_environment(env) #add the env var set to this test host env.each_pair do |var, value| add_env_var(var, value) end end #First path it finds for the command executable #@param [String] command The command executable to search for # # @return [String] Path to the searched executable or empty string if not found # #@example # host.which('ruby') def which(command) where_command = "cmd /C \"where #{command}\"" result = execute(where_command, :accept_all_exit_codes => true) return '' if result.empty? result end end beaker-4.30.0/lib/beaker/host/pswindows/file.rb000066400000000000000000000014671407603575700213320ustar00rootroot00000000000000module PSWindows::File include Beaker::CommandFactory def tmpfile(name = '') result = exec(powershell('[System.IO.Path]::GetTempFileName()')) result.stdout.chomp() end def tmpdir(name = '') tmp_path = exec(powershell('[System.IO.Path]::GetTempPath()')).stdout.chomp() if name == '' name = exec(powershell('[System.IO.Path]::GetRandomFileName()')).stdout.chomp() end exec(powershell("New-Item -Path '#{tmp_path}' -Force -Name '#{name}' -ItemType 'directory'")) File.join(tmp_path, name) end def path_split(paths) paths.split(';') end def cat(path) exec(powershell("type #{path}")).stdout end def file_exist?(path) result = exec(Beaker::Command.new("if exist #{path} echo true"), accept_all_exit_codes: true) result.stdout.strip == 'true' end end beaker-4.30.0/lib/beaker/host/pswindows/group.rb000066400000000000000000000017101407603575700215360ustar00rootroot00000000000000module PSWindows::Group include Beaker::CommandFactory def group_list(&block) execute('cmd /c echo "" | wmic group where localaccount="true" get name /format:value') do |result| groups = [] result.stdout.each_line do |line| groups << (line.match(/^Name=(.+)$/) or next)[1] end yield result if block_given? groups end end def group_get(name, &block) execute("net localgroup \"#{name}\"") do |result| fail_test "failed to get group #{name}" if result.exit_code != 0 yield result if block_given? result end end def group_gid(name) raise NotImplementedError, "Can't retrieve group gid on a Windows host" end def group_present(name, &block) execute("net localgroup /add \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end def group_absent(name, &block) execute("net localgroup /delete \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end end beaker-4.30.0/lib/beaker/host/pswindows/pkg.rb000066400000000000000000000026551407603575700211740ustar00rootroot00000000000000module PSWindows::Pkg include Beaker::CommandFactory def check_for_command(name) result = exec(Beaker::Command.new("where #{name}"), :accept_all_exit_codes => true) result.exit_code == 0 end def check_for_package(name) #HACK NOOP #raise "Cannot check for package #{name} on #{self}" 0 end def install_package(name, cmdline_args = '') #HACK NOOP #raise "Package #{name} cannot be installed on #{self}" 0 end def uninstall_package(name, cmdline_args = '') #HACK NOOP #raise "Package #{name} cannot be uninstalled on #{self}" 0 end #Examine the host system to determine the architecture, overrides default host determine_if_x86_64 so that wmic is used #@return [Boolean] true if x86_64, false otherwise def determine_if_x86_64 (identify_windows_architecture =~ /64/) == 0 end private # @api private def identify_windows_architecture arch = nil execute("wmic os get osarchitecture", :accept_all_exit_codes => true) do |result| arch = if result.exit_code == 0 result.stdout =~ /64/ ? '64' : '32' else identify_windows_architecture_from_os_name_for_win2003 end end arch end # @api private def identify_windows_architecture_from_os_name_for_win2003 arch = nil execute("wmic os get name", :accept_all_exit_codes => true) do |result| arch = result.stdout =~ /64/ ? '64' : '32' end arch end end beaker-4.30.0/lib/beaker/host/pswindows/user.rb000066400000000000000000000015111407603575700213570ustar00rootroot00000000000000module PSWindows::User include Beaker::CommandFactory def user_list(&block) execute('cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value') do |result| users = [] result.stdout.each_line do |line| users << (line.match(/^Name=(.+)/) or next)[1] end yield result if block_given? users end end def user_get(name, &block) execute("net user \"#{name}\"") do |result| fail_test "failed to get user #{name}" if result.exit_code != 0 yield result if block_given? result end end def user_present(name, &block) execute("net user /add \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end def user_absent(name, &block) execute("net user /delete \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end end beaker-4.30.0/lib/beaker/host/unix.rb000066400000000000000000000042261407603575700173350ustar00rootroot00000000000000[ 'host', 'command_factory', 'command', 'options' ].each do |lib| require "beaker/#{lib}" end module Unix class Host < Beaker::Host [ 'user', 'group', 'exec', 'pkg', 'file' ].each do |lib| require "beaker/host/unix/#{lib}" end include Unix::User include Unix::Group include Unix::File include Unix::Exec include Unix::Pkg def platform_defaults h = Beaker::Options::OptionsHash.new h.merge({ 'user' => 'root', 'group' => 'root', 'pathseparator' => ':', }) end # Determines which SSH Server is in use on this host # # @note This method is mostly a placeholder method, since only :openssh # can be returned at this time. Checkout {Windows::Host#determine_ssh_server} # for an example where work needs to be done to determine the answer # # @return [Symbol] Value for the SSH Server in use def determine_ssh_server :openssh end def external_copy_base return @external_copy_base if @external_copy_base @external_copy_base = '/root' variant, version, arch, codename = self['platform'].to_array # Solaris 10 uses / as the root user directory. Solaris 11 uses /root (like most). @external_copy_base = '/' if variant == 'solaris' && version == '10' @external_copy_base end # Tells you whether a host platform supports beaker's # {Beaker::HostPrebuiltSteps#set_env} method # # @return [String,nil] Reason message if set_env should be skipped, # nil if it should run. def skip_set_env? variant, version, arch, codename = self['platform'].to_array case variant when /^(f5|netscaler)$/ "no puppet-agent package for network device platform '#{variant}'" else nil end end # Validates that the host was setup correctly # # @return nil # @raise [ArgumentError] If the host is setup incorrectly, # this will be raised with the appropriate message def validate_setup nil end def initialize name, host_hash, options super @external_copy_base = nil end end end beaker-4.30.0/lib/beaker/host/unix/000077500000000000000000000000001407603575700170045ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/unix/exec.rb000066400000000000000000000452671407603575700202730ustar00rootroot00000000000000module Unix::Exec include Beaker::CommandFactory # Reboots the host, comparing uptime values to verify success # @param [Integer] wait_time How long to wait after sending the reboot # command before attempting to check in on the host # @param [Integer] max_connection_tries How many times to retry connecting to # host after reboot. Note that there is an fibbonacci # backoff when attempting retries so the time spent # waiting on this can grow quickly. # @param [Integer] uptime_retries How many times to check to see if the value of the uptime has reset. # # Will throw an exception RebootFailure if it fails def reboot(wait_time=10, max_connection_tries=9, uptime_retries=18) require 'time' attempts = 0 # Some systems don't support 'last -F reboot' but it has second granularity boot_time_cmd = 'last -F reboot || who -b' # Try to match all of the common formats for 'last' and 'who' current_year = Time.now.strftime("%Y") boot_time_regex = Regexp.new(%{((?:#{(Date::ABBR_DAYNAMES + Date::ABBR_MONTHNAMES).compact.join('|')}|#{current_year}).+?(\\d+:\\d+)+?(?::(\\d+).+?#{current_year})?)}) original_boot_time_str = nil original_boot_time_line = nil begin attempts += 1 # Number of seconds to sleep before rebooting. reboot_sleep = 1 original_boot_time_str = exec(Beaker::Command.new(boot_time_cmd), {:max_connection_tries => max_connection_tries, :silent => true}).stdout original_boot_time_line = original_boot_time_str.lines.grep(/boot/).first raise Beaker::Host::RebootWarning, "Could not find system boot time using '#{boot_time_cmd}': '#{original_boot_time_str}'" unless original_boot_time_line original_boot_time_matches = original_boot_time_line.scan(boot_time_regex).last raise Beaker::Host::RebootWarning, "Found no valid times in '#{original_boot_time_line}'" unless original_boot_time_matches original_boot_time = Time.parse(original_boot_time_matches.first) unless original_boot_time_matches.last reboot_sleep = (61 - Time.now.strftime("%S").to_i) end @logger.notify("Sleeping #{reboot_sleep} seconds before rebooting") sleep(reboot_sleep) exec(Beaker::Command.new('/bin/systemctl reboot -i || reboot || /sbin/shutdown -r now'), :expect_connection_failure => true) rescue ArgumentError => e raise Beaker::Host::RebootFailure, "Unable to parse time: #{e.message}" rescue Beaker::Host::RebootWarning => e raise if attempts > uptime_retries @logger.warn(e.message) @logger.warn("Retrying #{uptime_retries - attempts} more times.") retry rescue StandardError => e raise if attempts > uptime_retries @logger.warn("Unexpected Exception: #{e.message}") @logger.warn("Retrying #{uptime_retries - attempts} more times.") @logger.warn(e.backtrace[0,3].join("\n")) @logger.debug(e.backtrace.join("\n")) retry end attempts = 0 begin attempts += 1 # give the host a little time to shutdown @logger.debug("Waiting #{wait_time} for host to shut down.") sleep wait_time # Accept all exit codes because this may fail due to the parallel nature of systemd current_boot_time_str = exec(Beaker::Command.new(boot_time_cmd), {:max_connection_tries => max_connection_tries, :silent => true, :accept_all_exit_codes => true}).stdout current_boot_time_line = current_boot_time_str.lines.grep(/boot/).first raise Beaker::Host::RebootWarning, "Could not find system boot time using '#{boot_time_cmd}': '#{current_boot_time_str}'" unless current_boot_time_line current_boot_time_matches = current_boot_time_line.scan(boot_time_regex).last raise Beaker::Host::RebootWarning, "Found no valid times in '#{current_boot_time_line}'" unless current_boot_time_matches current_boot_time = Time.parse(current_boot_time_matches.first) @logger.debug("Original Boot Time: #{original_boot_time}") @logger.debug("Current Boot Time: #{current_boot_time}") # If this is *exactly* the same then there is really no good way to detect a reboot if current_boot_time == original_boot_time raise Beaker::Host::RebootFailure, "Boot time did not reset. Reboot appears to have failed." end rescue ArgumentError => e raise Beaker::Host::RebootFailure, "Unable to parse time: #{e.message}" rescue Beaker::Host::RebootFailure => e raise rescue Beaker::Host::RebootWarning => e raise if attempts > uptime_retries @logger.warn(e.message) @logger.warn("Retrying #{uptime_retries - attempts} more times.") retry rescue StandardError => e raise if attempts > uptime_retries @logger.warn("Unexpected Exception: #{e.message}") @logger.warn("Retrying #{uptime_retries - attempts} more times.") @logger.warn(e.backtrace[0,3].join("\n")) @logger.debug(e.backtrace.join("\n")) retry end end def echo(msg, abs=true) (abs ? '/bin/echo' : 'echo') + " #{msg}" end def touch(file, abs=true) (abs ? '/bin/touch' : 'touch') + " #{file}" end # Update ModifiedDate on a file # @param [String] file Path to the file # @param [String] timestamp Timestamp to set def modified_at(file, timestamp = nil) require 'date' time = timestamp ? DateTime.parse("#{timestamp}") : DateTime.now timestamp = time.strftime('%Y%m%d%H%M') execute("/bin/touch -mt #{timestamp} #{file}") end def path '/bin:/usr/bin' end def get_ip if self['platform'].include?('solaris') || self['platform'].include?('osx') execute("ifconfig -a inet| awk '/broadcast/ {print $2}' | cut -d/ -f1 | head -1").strip else pipe_cmd = "#{self['hypervisor']}".include?('vagrant') ? 'tail' : 'head' execute("ip a | awk '/global/{print$2}' | cut -d/ -f1 | #{pipe_cmd} -1").strip end end # Create the provided directory structure on the host # @param [String] dir The directory structure to create on the host # @return [Boolean] True, if directory construction succeeded, otherwise False def mkdir_p dir cmd = "mkdir -p #{dir}" result = exec(Beaker::Command.new(cmd), :acceptable_exit_codes => [0, 1]) result.exit_code == 0 end # Recursively remove the path provided # @param [String] path The path to remove def rm_rf path execute("rm -rf #{path}") end # Move the origin to destination. The destination is removed prior to moving. # @param [String] orig The origin path # @param [String] dest the destination path # @param [Boolean] rm Remove the destination prior to move def mv orig, dest, rm=true rm_rf dest unless !rm execute("mv #{orig} #{dest}") end # Attempt to ping the provided target hostname # @param [String] target The hostname to ping # @param [Integer] attempts Amount of times to attempt ping before giving up # @return [Boolean] true of ping successful, overwise false def ping target, attempts=5 try = 0 while try < attempts do result = exec(Beaker::Command.new("ping -c 1 #{target}"), :accept_all_exit_codes => true) if result.exit_code == 0 return true end try+=1 end result.exit_code == 0 end # Converts the provided environment file to a new shell script in /etc/profile.d, then sources that file. # This is for sles based hosts. # @param [String] env_file The ssh environment file to read from def mirror_env_to_profile_d env_file if self[:platform] =~ /opensuse|sles-/ @logger.debug("mirroring environment to /etc/profile.d on opensuse/sles platform host") cur_env = exec(Beaker::Command.new("cat #{env_file}")).stdout shell_env = '' cur_env.each_line do |env_line| shell_env << "export #{env_line}" end #here doc it over exec(Beaker::Command.new("cat << EOF > #{self[:profile_d_env_file]}\n#{shell_env}EOF")) #set permissions exec(Beaker::Command.new("chmod +x #{self[:profile_d_env_file]}")) #keep it current exec(Beaker::Command.new("source #{self[:profile_d_env_file]}")) else #noop @logger.debug("will not mirror environment to /etc/profile.d on non-sles platform host") end end #Add the provided key/val to the current ssh environment #@param [String] key The key to add the value to #@param [String] val The value for the key #@example # host.add_env_var('PATH', '/usr/bin:PATH') def add_env_var key, val key = key.to_s env_file = self[:ssh_env_file] escaped_val = Regexp.escape(val).gsub('/', '\/').gsub(';', '\;') #see if the key/value pair already exists if exec(Beaker::Command.new("grep ^#{key}=.*#{escaped_val} #{env_file}"), :accept_all_exit_codes => true ).exit_code == 0 return #nothing to do here, key value pair already exists #see if the key already exists elsif exec(Beaker::Command.new("grep ^#{key}= #{env_file}"), :accept_all_exit_codes => true ).exit_code == 0 exec(Beaker::SedCommand.new(self['platform'], "s/^#{key}=/#{key}=#{escaped_val}:/", env_file)) else exec(Beaker::Command.new("echo \"#{key}=#{val}\" >> #{env_file}")) end #update the profile.d to current state #match it to the contents of ssh_env_file mirror_env_to_profile_d(env_file) end #Delete the provided key/val from the current ssh environment #@param [String] key The key to delete the value from #@param [String] val The value to delete for the key #@example # host.delete_env_var('PATH', '/usr/bin:PATH') def delete_env_var key, val key = key.to_s env_file = self[:ssh_env_file] val = Regexp.escape(val).gsub('/', '\/').gsub(';', '\;') #if the key only has that single value remove the entire line exec(Beaker::SedCommand.new(self['platform'], "/#{key}=#{val}$/d", env_file)) #value in middle of list exec(Beaker::SedCommand.new(self['platform'], "s/#{key}=\\(.*\\)[;:]#{val}/#{key}=\\1/", env_file)) #value in start of list exec(Beaker::SedCommand.new(self['platform'], "s/#{key}=#{val}[;:]/#{key}=/", env_file)) #update the profile.d to current state #match it to the contents of ssh_env_file mirror_env_to_profile_d(env_file) end #Return the value of a specific env var #@param [String] key The key to look for #@example # host.get_env_var('path') def get_env_var key key = key.to_s exec(Beaker::Command.new("env | grep ^#{key}="), :accept_all_exit_codes => true).stdout.chomp end #Delete the environment variable from the current ssh environment #@param [String] key The key to delete #@example # host.clear_env_var('PATH') def clear_env_var key key = key.to_s env_file = self[:ssh_env_file] #remove entire line exec(Beaker::SedCommand.new(self['platform'], "/^#{key}=.*$/d", env_file)) #update the profile.d to current state #match it to the contents of ssh_env_file mirror_env_to_profile_d(env_file) end # Restarts the SSH service. # # @return [Result] result of restarting the SSH service def ssh_service_restart case self['platform'] when /debian|ubuntu|cumulus|huaweios/ exec(Beaker::Command.new("service ssh restart")) when /el-7|centos-7|redhat-7|oracle-7|scientific-7|eos-7|el-8|centos-8|redhat-8|oracle-8|fedora-(1[4-9]|2[0-9]|3[0-9])|archlinux-/ exec(Beaker::Command.new("systemctl restart sshd.service")) when /el-|centos|fedora|redhat|oracle|scientific|eos/ exec(Beaker::Command.new("/sbin/service sshd restart")) when /opensuse|sles/ exec(Beaker::Command.new("/usr/sbin/rcsshd restart")) when /solaris/ exec(Beaker::Command.new("svcadm restart svc:/network/ssh:default")) when /(free|open)bsd/ exec(Beaker::Command.new("sudo /etc/rc.d/sshd restart")) else raise ArgumentError, "Unsupported Platform: '#{self['platform']}'" end end # Sets the PermitUserEnvironment setting & restarts the SSH service. # # @api private # @return [Result] result of the command restarting the SSH service # (from {#ssh_service_restart}). def ssh_permit_user_environment case self['platform'] when /debian|ubuntu|cumulus|huaweios|archlinux/ directory = tmpdir() exec(Beaker::Command.new("echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit")) exec(Beaker::Command.new("mv #{directory}/sshd_config.permit /etc/ssh/sshd_config")) exec(Beaker::Command.new("echo '' >/etc/environment")) if self['platform'] =~ /ubuntu-20.04/ when /el-7|centos-7|redhat-7|oracle-7|scientific-7|eos-7|el-8|centos-8|redhat-8|oracle-8/ directory = tmpdir() exec(Beaker::Command.new("echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit")) exec(Beaker::Command.new("mv #{directory}/sshd_config.permit /etc/ssh/sshd_config")) when /el-|centos|fedora|redhat|oracle|scientific|eos/ directory = tmpdir() exec(Beaker::Command.new("echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit")) exec(Beaker::Command.new("mv #{directory}/sshd_config.permit /etc/ssh/sshd_config")) when /opensuse|sles/ directory = tmpdir() exec(Beaker::Command.new("echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit")) exec(Beaker::Command.new("mv #{directory}/sshd_config.permit /etc/ssh/sshd_config")) when /solaris/ # kept solaris here because refactoring it into its own Host module # conflicts with the solaris hypervisor that already exists directory = tmpdir() exec(Beaker::Command.new("echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit")) exec(Beaker::Command.new("mv #{directory}/sshd_config.permit /etc/ssh/sshd_config")) when /(free|open)bsd/ exec(Beaker::Command.new("sudo perl -pi -e 's/^#?PermitUserEnvironment no/PermitUserEnvironment yes/' /etc/ssh/sshd_config"), {:pty => true} ) else raise ArgumentError, "Unsupported Platform: '#{self['platform']}'" end ssh_service_restart() end # Construct the environment string for this command # # @param [Hash{String=>String}] env An optional Hash containing # key-value pairs to be treated # as environment variables that # should be set for the duration # of the puppet command. # # @return [String] Returns a string containing command line arguments that # will ensure the environment is correctly set for the # given host. def environment_string env return '' if env.empty? env_array = self.environment_variable_string_pair_array( env ) environment_string = env_array.join(' ') "env #{environment_string}" end def environment_variable_string_pair_array env env_array = [] env.each_key do |key| val = env[key] if val.is_a?(Array) val = val.join(':') else val = val.to_s end # doing this for the key itself & the upcase'd version allows us to remain # backwards compatible # TODO: (Next Major Version) get rid of upcase'd version key_str = key.to_s keys = [key_str] keys << key_str.upcase if key_str.upcase != key_str keys.each do |env_key| env_array << "#{env_key}=\"#{val}\"" end end env_array end # Gets the specific prepend commands as needed for this host # # @param [String] command Command to be executed # @param [String] user_pc List of user-specified commands to prepend # @param [Hash] opts optional parameters # # @return [String] Command string as needed for this host def prepend_commands(command = '', user_pc = '', opts = {}) user_pc end # Gets the specific append commands as needed for this host # # @param [String] command Command to be executed # @param [String] user_ac List of user-specified commands to append # @param [Hash] opts optional parameters # # @return [String] Command string as needed for this host def append_commands(command = '', user_ac = '', opts = {}) user_ac end # Fills the user SSH environment file. # # @param [Hash{String=>String}] env Environment variables to set on the system, # in the form of a hash of String variable # names to their corresponding String values. # # @api private # @return nil def ssh_set_user_environment(env) #ensure that ~/.ssh/environment exists ssh_env_file_dir = Pathname.new(self[:ssh_env_file]).dirname mkdir_p(ssh_env_file_dir) exec(Beaker::Command.new("chmod 0600 #{ssh_env_file_dir}")) exec(Beaker::Command.new("touch #{self[:ssh_env_file]}")) #add the constructed env vars to this host add_env_var('PATH', '$PATH') # FIXME if self['platform'] =~ /openbsd-(\d)\.?(\d)-(.+)/ version = "#{$1}.#{$2}" arch = $3 arch = 'amd64' if ['x64', 'x86_64'].include?(arch) add_env_var('PKG_PATH', "http://ftp.openbsd.org/pub/OpenBSD/#{version}/packages/#{arch}/") elsif self['platform'] =~ /solaris-10/ add_env_var('PATH', '/opt/csw/bin') end #add the env var set to this test host env.each_pair do |var, value| add_env_var(var, value) end end # Checks if selinux is enabled # # @return [Boolean] true if selinux is enabled, false otherwise def selinux_enabled?() exec(Beaker::Command.new("sudo selinuxenabled"), :accept_all_exit_codes => true).exit_code == 0 end def enable_remote_rsyslog(server = 'rsyslog.ops.puppetlabs.net', port = 514) if self['platform'] !~ /ubuntu/ @logger.warn "Enabling rsyslog is only implemented for ubuntu hosts" return end commands = [ "echo '*.* @#{server}:#{port}' >> /etc/rsyslog.d/51-sendrsyslogs.conf", 'systemctl restart rsyslog' ] commands.each do |command| exec(Beaker::Command.new(command)) end true end #First path it finds for the command executable #@param [String] command The command executable to search for # # @return [String] Path to the searched executable or empty string if not found # #@example # host.which('ruby') def which(command) unless @which_command if execute('type -P true', :accept_all_exit_codes => true).empty? if execute('which true', :accept_all_exit_codes => true).empty? raise ArgumentError, "Could not find suitable 'which' command" else @which_command = 'which' end else @which_command = 'type -P' end end result = execute("#{@which_command} #{command}", :accept_all_exit_codes => true) return '' if result.empty? result end end beaker-4.30.0/lib/beaker/host/unix/file.rb000066400000000000000000000145271407603575700202610ustar00rootroot00000000000000module Unix::File include Beaker::CommandFactory def tmpfile(name = '') execute("mktemp -t #{name}.XXXXXX") end def tmpdir(name = '') execute("mktemp -dt #{name}.XXXXXX") end def system_temp_path '/tmp' end # Change user ownership of a path # # @see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/chown.html # # @note To maintain argument order consistency with the underlying # syscall, avoid having to specify nil arguments, and not do # anything hacky with the arguments list, this method does not # allow you to modify group ownership. Use Host::chgrp instead. # @param [String] user User to chown to # @param [String] path Path to chown # @param [Boolean] recursive Whether to pass the recursive flag # # @return [Beaker::Result] result of command execution def chown(user, path, recursive=false) execute("chown #{recursive ? '-R ' : ''}#{user} #{path}") end def chmod(mod, path, recursive=false) execute("chmod #{recursive ? '-R ' : ''}#{mod} #{path}") end # Change group ownership of a path # # @see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/chgrp.html # # @param [String] group Group to chgrp to # @param [String] path Path to chgrp # @param [Boolean] recursive Whether to pass the recursive flag # # @return [Beaker::Result] result of command execution def chgrp(group, path, recursive=false) execute("chgrp #{recursive ? '-R ' : ''}#{group} #{path}") end # List long output of a path # # @see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html # # @param [String] path Path to list properties of # # @return [Beaker::Result] result of command execution def ls_ld(path) execute("ls -ld #{path}") end def cat(path) execute("cat #{path}") end # Handles any changes needed in a path for SCP # # @param [String] path File path to SCP to # # @return [String] path, changed if needed due to host # constraints def scp_path(path) path end def path_split(paths) paths.split(':') end def file_exist?(path) result = exec(Beaker::Command.new("test -e #{path}"), :acceptable_exit_codes => [0, 1]) result.exit_code == 0 end # Gets the config dir location for package information # # @raise [ArgumentError] For an unknown platform # # @return [String] Path to package config dir def package_config_dir case self['platform'] when /fedora|el-|redhat|centos/ '/etc/yum.repos.d/' when /opensuse|sles/ '/etc/zypp/repos.d/' when /debian|ubuntu|cumulus|huaweios/ '/etc/apt/sources.list.d' else msg = "package config dir unknown for platform '#{self['platform']}'" raise ArgumentError, msg end end # Returns the repo filename for a given package & version for a platform # # @param [String] package_name Name of the package # @param [String] build_version Version string of the package # # @raise [ArgumentError] For an unknown platform # # @return [String] Filename of the repo def repo_filename(package_name, build_version) variant, version, arch, codename = self['platform'].to_array repo_filename = "pl-%s-%s-" % [ package_name, build_version ] case variant when /fedora|el|redhat|centos|cisco_nexus|cisco_ios_xr|opensuse|sles/ variant = 'el' if ['centos', 'redhat'].include?(variant) variant = 'redhatfips' if self['packaging_platform'] =~ /redhatfips/ if variant == 'cisco_nexus' variant = 'cisco-wrlinux' version = '5' end if variant == 'cisco_ios_xr' variant = 'cisco-wrlinux' version = '7' end fedora_prefix = ((variant == 'fedora') ? 'f' : '') pattern = "%s-%s%s-%s.repo" repo_filename << pattern % [ variant, fedora_prefix, version, arch ] when /debian|ubuntu|cumulus|huaweios/ codename = variant if variant == 'cumulus' || variant == 'huaweios' repo_filename << "%s.list" % [ codename ] else msg = "#repo_filename: repo filename pattern not known for platform '#{self['platform']}'" raise ArgumentError, msg end repo_filename end # Gets the repo type for the given platform # # @raise [ArgumentError] For an unknown platform # # @return [String] Type of repo (rpm|deb) def repo_type case self['platform'] when /fedora|el-|redhat|centos|opensuse|sles/ 'rpm' when /debian|ubuntu|cumulus|huaweios/ 'deb' else msg = "#repo_type: repo type not known for platform '#{self['platform']}'" raise ArgumentError, msg end end # Returns the noask file text for Solaris hosts # # @raise [ArgumentError] If called on a host with a platform that's not Solaris # # @return [String] the text of the noask file def noask_file_text variant, version, arch, codename = self['platform'].to_array if variant == 'solaris' && version == '10' noask = < true) case self['platform'] when /solaris-10/ # solaris 10 appears to have considered `which` to have run successfully, # even if the command didn't exist, so it'll return a 0 exit code in # either case. Instead we match for the phrase output when a match isn't # found: "no #{name} in $PATH", reversing it to match our API !( result.stdout.match(/^no\ #{name}\ in\ /) ) else result.exit_code == 0 end end def check_for_package(name, opts = {}) opts = {:accept_all_exit_codes => true}.merge(opts) case self['platform'] when /sles-10/ result = execute("zypper se -i --match-exact #{name}", opts) { |result| result } result.stdout =~ /No packages found/ ? (return false) : (return result.exit_code == 0) when /opensuse|sles-/ if !self[:sles_rpmkeys_nightly_pl_imported] # The `:sles_rpmkeys_nightly_pl_imported` key is only read here at this # time. It's just to make sure that we only do the key import once, & # isn't for setting or use outside of beaker. execute('rpmkeys --import http://nightlies.puppetlabs.com/07BB6C57', opts) self[:sles_rpmkeys_nightly_pl_imported] = true end result = execute("zypper --gpg-auto-import-keys se -i --match-exact #{name}", opts) { |result| result } when /el-4/ @logger.debug("Package query not supported on rhel4") return false when /cisco|fedora|centos|redhat|eos|el-/ result = execute("rpm -q #{name}", opts) { |result| result } when /ubuntu|debian|cumulus|huaweios/ result = execute("dpkg -s #{name}", opts) { |result| result } when /solaris-11/ result = execute("pkg info #{name}", opts) { |result| result } when /solaris-10/ result = execute("pkginfo #{name}", opts) { |result| result } if result.exit_code == 1 result = execute("pkginfo CSW#{name}", opts) { |result| result } end when /openbsd/ result = execute("pkg_info #{name}", opts) { |result| result } when /archlinux/ result = execute("pacman -Q #{name}", opts) { |result| result } else raise "Package #{name} cannot be queried on #{self}" end result.exit_code == 0 end # If apt has not been updated since the last repo deployment it is # updated. Otherwise this is a noop def update_apt_if_needed if self['platform'] =~ /debian|ubuntu|cumulus|huaweios/ if @apt_needs_update execute("apt-get update") @apt_needs_update = false end end end # Arch Linux is a rolling release distribution. We need to ensure that it is up2date # Except for the kernel. An upgrade will purge the modules for the currently running kernel def update_pacman_if_needed if self['platform'] =~ /archlinux/ if @pacman_needs_update execute("pacman --sync --noconfirm --noprogressbar --refresh --sysupgrade --ignore linux --ignore linux-docs --ignore linux-headers") @pacman_needs_update = false end end end def install_package(name, cmdline_args = '', version = nil, opts = {}) case self['platform'] when /opensuse|sles-/ execute("zypper --non-interactive --gpg-auto-import-keys in #{name}", opts) when /el-4/ @logger.debug("Package installation not supported on rhel4") when /fedora-(2[2-9]|3[0-9])/ if version name = "#{name}-#{version}" end execute("dnf -y #{cmdline_args} install #{name}", opts) when /cisco|fedora|centos|redhat|eos|el-/ if version name = "#{name}-#{version}" end execute("yum -y #{cmdline_args} install #{name}", opts) when /ubuntu|debian|cumulus|huaweios/ if version name = "#{name}=#{version}" end update_apt_if_needed execute("apt-get install --force-yes #{cmdline_args} -y #{name}", opts) when /solaris-11/ if opts[:acceptable_exit_codes] opts[:acceptable_exit_codes] << 4 else opts[:acceptable_exit_codes] = [0, 4] unless opts[:accept_all_exit_codes] end execute("pkg #{cmdline_args} install #{name}", opts) when /solaris-10/ if ! check_for_command('pkgutil') # https://www.opencsw.org/package/pkgutil/ noask_text = self.noask_file_text noask_file = File.join(external_copy_base, 'noask') create_remote_file(self, noask_file, noask_text) execute("pkgadd -d http://get.opencsw.org/now -a #{noask_file} -n all", opts) execute('/opt/csw/bin/pkgutil -U', opts) execute('/opt/csw/bin/pkgutil -y -i pkgutil', opts) end execute("pkgutil -i -y #{cmdline_args} #{name}", opts) when /openbsd/ begin execute("pkg_add -I #{cmdline_args} #{name}", opts) do |command| # Handles where there are multiple rubies, installs the latest one if command.stderr =~ /^Ambiguous: #{name} could be (.+)$/ name = $1.chomp.split(' ').collect { |x| x =~ /-(\d[^-p]+)/ [x, $1] }.select { |x| # Blacklist Ruby 2.2.0+ for the sake of Puppet 3.x Gem::Version.new(x[1]) < Gem::Version.new('2.2.0') }.sort { |a,b| Gem::Version.new(b[1]) <=> Gem::Version.new(a[1]) }.collect { |x| x[0] }.first raise ArgumentException end # If the package advises symlinks to be created, do it command.stdout.split(/\n/).select { |x| x =~ /^\s+ln\s/ }.each do |ln| execute(ln, opts) end end rescue retry end when /archlinux/ update_pacman_if_needed execute("pacman -S --noconfirm #{cmdline_args} #{name}", opts) else raise "Package #{name} cannot be installed on #{self}" end end # Install a package using RPM # # @param [String] name The name of the package to install. It # may be a filename or a URL. # @param [String] cmdline_args Additional command line arguments for # the package manager. # @option opts [String] :package_proxy A proxy of form http://host:port # # @return nil # @api public def install_package_with_rpm(name, cmdline_args = '', opts = {}) proxy = '' if name =~ /^http/ and opts[:package_proxy] proxy = extract_rpm_proxy_options(opts[:package_proxy]) end execute("rpm #{cmdline_args} -Uvh #{name} #{proxy}") end def uninstall_package(name, cmdline_args = '', opts = {}) case self['platform'] when /opensuse|sles-/ execute("zypper --non-interactive rm #{name}", opts) when /el-4/ @logger.debug("Package uninstallation not supported on rhel4") when /edora-(2[2-9]|3[0-9])/ execute("dnf -y #{cmdline_args} remove #{name}", opts) when /cisco|fedora|centos|redhat|eos|el-/ execute("yum -y #{cmdline_args} remove #{name}", opts) when /ubuntu|debian|cumulus|huaweios/ execute("apt-get purge #{cmdline_args} -y #{name}", opts) when /solaris-11/ execute("pkg #{cmdline_args} uninstall #{name}", opts) when /solaris-10/ execute("pkgrm -n #{cmdline_args} #{name}", opts) when /aix/ execute("rpm #{cmdline_args} -e #{name}", opts) when /archlinux/ execute("pacman -R --noconfirm #{cmdline_args} #{name}", opts) else raise "Package #{name} cannot be installed on #{self}" end end # Upgrade an installed package to the latest available version # # @param [String] name The name of the package to update # @param [String] cmdline_args Additional command line arguments for # the package manager def upgrade_package(name, cmdline_args = '', opts = {}) case self['platform'] when /opensuse|sles-/ execute("zypper --non-interactive --no-gpg-checks up #{name}", opts) when /el-4/ @logger.debug("Package upgrade is not supported on rhel4") when /fedora-(2[2-9]|3[0-9])/ execute("dnf -y #{cmdline_args} update #{name}", opts) when /cisco|fedora|centos|redhat|eos|el-/ execute("yum -y #{cmdline_args} update #{name}", opts) when /ubuntu|debian|cumulus|huaweios/ update_apt_if_needed execute("apt-get install -o Dpkg::Options::='--force-confold' #{cmdline_args} -y --force-yes #{name}", opts) when /solaris-11/ if opts[:acceptable_exit_codes] opts[:acceptable_exit_codes] << 4 else opts[:acceptable_exit_codes] = [0, 4] unless opts[:accept_all_exit_codes] end execute("pkg #{cmdline_args} update #{name}", opts) when /solaris-10/ execute("pkgutil -u -y #{cmdline_args} #{name}", opts) else raise "Package #{name} cannot be upgraded on #{self}" end end # Deploy apt configuration generated by the packaging tooling # # @note Due to the debian use of codenames in repos, the # DEBIAN_PLATFORM_CODENAMES map must be kept up-to-date as # support for new versions is added. # # @note See {Beaker::DSL::Helpers::HostHelpers#deploy_package_repo} for info on # params # @deprecated no longer used in beaker, beaker-puppet, or beaker-pe # @visibility private def deploy_apt_repo(path, name, version) codename = self['platform'].codename if codename.nil? @logger.warn "Could not determine codename for debian platform #{self['platform']}. Skipping deployment of repo #{name}" return end repo_file = "#{path}/deb/pl-#{name}-#{version}-#{codename}.list" do_scp_to repo_file, "/etc/apt/sources.list.d/#{name}.list", {} @apt_needs_update = true end # Deploy yum configuration generated by the packaging tooling # # @note See {Beaker::DSL::Helpers::HostHelpers#deploy_package_repo} for info on # params # @deprecated no longer used in beaker, beaker-puppet, or beaker-pe # @visibility private def deploy_yum_repo(path, name, version) repo_file = "#{path}/rpm/pl-#{name}-#{version}-repos-pe-#{self['platform']}.repo" do_scp_to repo_file, "/etc/yum.repos.d/#{name}.repo", {} end # Deploy zypper repo configuration generated by the packaging tooling # # @note See {Beaker::DSL::Helpers::HostHelpers#deploy_package_repo} for info on # params # @deprecated no longer used in beaker, beaker-puppet, or beaker-pe # @visibility private def deploy_zyp_repo(path, name, version) repo_file = "#{path}/rpm/pl-#{name}-#{version}-repos-pe-#{self['platform']}.repo" repo = IniFile.load(repo_file) repo_name = repo.sections[0] repo_url = repo[repo_name]["baseurl"] execute("zypper ar -t YUM #{repo_url} #{repo_name}") end # Deploy configuration generated by the packaging tooling to this host. # # This method calls one of #deploy_apt_repo, #deploy_yum_repo, or # #deploy_zyp_repo depending on the platform of this Host. # # @note See {Beaker::DSL::Helpers::HostHelpers#deploy_package_repo} for info on # params # @deprecated no longer used in beaker, beaker-puppet, or beaker-pe # @visibility private def deploy_package_repo(path, name, version) if not File.exists? path @logger.warn "Was asked to deploy package repository from #{path}, but it doesn't exist!" return end case self['platform'] when /el-4/ @logger.debug("Package repo deploy is not supported on rhel4") when /fedora|centos|redhat|eos|el-/ deploy_yum_repo(path, name, version) when /ubuntu|debian|cumulus|huaweios/ deploy_apt_repo(path, name, version) when /opensuse|sles/ deploy_zyp_repo(path, name, version) else # solaris, windows raise "Package repo cannot be deployed on #{self}; the platform is not supported" end end #Examine the host system to determine the architecture #@return [Boolean] true if x86_64, false otherwise def determine_if_x86_64 if self[:platform] =~ /solaris/ result = exec(Beaker::Command.new("uname -a | grep x86_64"), :accept_all_exit_codes => true) result.exit_code == 0 else result = exec(Beaker::Command.new("arch | grep x86_64"), :accept_all_exit_codes => true) result.exit_code == 0 end end # Extract RPM command's proxy options from URL # # @param [String] url A URL of form http://host:port # @return [String] httpproxy and httport options for rpm # # @raise [StandardError] When encountering a string that # cannot be parsed # @api private def extract_rpm_proxy_options(url) begin host, port = url.match(/https?:\/\/(.*):(\d*)/)[1,2] raise if host.empty? or port.empty? "--httpproxy #{host} --httpport #{port}" rescue raise "Cannot extract host and port from '#{url}'" end end # Gets the path & file name for the puppet agent dev package on Unix # # @param [String] puppet_collection Name of the puppet collection to use # @param [String] puppet_agent_version Version of puppet agent to get # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @note Solaris does require :download_url to be set on the opts argument # in order to check for builds on the builds server # # @raise [ArgumentError] If one of the two required parameters (puppet_collection, # puppet_agent_version) is either not passed or set to nil # # @return [String, String] Path to the directory and filename of the package, respectively def solaris_puppet_agent_dev_package_info( puppet_collection = nil, puppet_agent_version = nil, opts = {} ) error_message = "Must provide %s argument to get puppet agent package information" raise ArgumentError, error_message % "puppet_collection" unless puppet_collection raise ArgumentError, error_message % "puppet_agent_version" unless puppet_agent_version raise ArgumentError, error_message % "opts[:download_url]" unless opts[:download_url] variant, version, arch, codename = self['platform'].to_array version = version.split('.')[0] # packages are only published for major versions platform_error = "Incorrect platform '#{variant}' for #solaris_puppet_agent_dev_package_info" raise ArgumentError, platform_error if variant != 'solaris' if arch == 'x86_64' arch = 'i386' end release_path_end = "solaris/#{version}/#{puppet_collection}" solaris_revision_conjunction = '-' revision = '1' if version == '10' solaris_release_version = '' pkg_suffix = 'pkg.gz' solaris_name_conjunction = '-' component_version = puppet_agent_version elsif version == '11' # Ref: # http://www.oracle.com/technetwork/articles/servers-storage-admin/ips-package-versioning-2232906.html # # Example to show package name components: # Full package name: puppet-agent@1.2.5.38.6813,5.11-1.sparc.p5p # Schema: .. solaris_release_version = ',5.11' # injecting comma to prevent from adding another var pkg_suffix = 'p5p' solaris_name_conjunction = '@' component_version = puppet_agent_version.dup component_version.gsub!(/[a-zA-Z]/, '') component_version.gsub!(/(^-)|(-$)/, '') # Here we strip leading 0 from version components but leave # singular 0 on their own. component_version = component_version.split('-').join('.') component_version = component_version.split('.').map(&:to_i).join('.') end release_file_base = "puppet-agent#{solaris_name_conjunction}#{component_version}#{solaris_release_version}" release_file_end = "#{arch}.#{pkg_suffix}" release_file = "#{release_file_base}#{solaris_revision_conjunction}#{revision}.#{release_file_end}" if not link_exists?("#{opts[:download_url]}/#{release_path_end}/#{release_file}") release_file = "#{release_file_base}.#{release_file_end}" end return release_path_end, release_file end # Gets the path & file name for the puppet agent dev package on Unix # # @param [String] puppet_collection Name of the puppet collection to use # @param [String] puppet_agent_version Version of puppet agent to get # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @note Solaris & OSX do require some options to be set. See # {#solaris_puppet_agent_dev_package_info} & # {Mac::Pkg#puppet_agent_dev_package_info} for more details # # @raise [ArgumentError] If one of the two required parameters (puppet_collection, # puppet_agent_version) is either not passed or set to nil # # @return [String, String] Path to the directory and filename of the package, respectively def puppet_agent_dev_package_info( puppet_collection = nil, puppet_agent_version = nil, opts = {} ) error_message = "Must provide %s argument to get puppet agent dev package information" raise ArgumentError, error_message % "puppet_collection" unless puppet_collection raise ArgumentError, error_message % "puppet_agent_version" unless puppet_agent_version variant, version, arch, codename = self['platform'].to_array case variant when /^(solaris)$/ release_path_end, release_file = solaris_puppet_agent_dev_package_info( puppet_collection, puppet_agent_version, opts ) when /^(opensuse|sles|aix|el|centos|oracle|redhat|scientific)$/ variant = 'el' if variant.match(/(?:el|centos|oracle|redhat|scientific)/) variant = 'sles' if variant == 'opensuse' if variant == 'aix' arch = 'ppc' if arch == 'power' version_x, version_y = /^(\d+)\.(\d+)/.match(puppet_agent_version).captures.map(&:to_i) if version_x < 5 || version_x == 5 && version_y < 99 # 5.99.z indicates pre-release puppet6 version = '7.1' if version == '7.2' else version = '6.1' end end release_path_end = "#{variant}/#{version}/#{puppet_collection}/#{arch}" release_file = "puppet-agent-#{puppet_agent_version}-1.#{variant}#{version}.#{arch}.rpm" else msg = "puppet_agent dev package info unknown for platform '#{self['platform']}'" raise ArgumentError, msg end return release_path_end, release_file end # Gets host-specific information for PE promoted puppet-agent packages # # @param [String] puppet_collection Name of the puppet collection to use # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @return [String, String, String] Host-specific information for packages # 1. release_path_end Suffix for the release_path. Used on Windows. Check # {Windows::Pkg#pe_puppet_agent_promoted_package_info} to see usage. # 2. release_file Path to the file on release build servers # 3. download_file Filename for the package itself def pe_puppet_agent_promoted_package_info( puppet_collection = nil, opts = {} ) error_message = "Must provide %s argument to get puppet agent dev package information" raise ArgumentError, error_message % "puppet_collection" unless puppet_collection variant, version, arch, codename = self['platform'].to_array case variant when /^(fedora|el|centos|redhat|opensuse|sles)$/ variant = ((['centos', 'redhat'].include?(variant)) ? 'el' : variant) release_file = "/repos/#{variant}/#{version}/#{puppet_collection}/#{arch}/puppet-agent-*.rpm" download_file = "puppet-agent-#{variant}-#{version}-#{arch}.tar.gz" when /^(debian|ubuntu|cumulus)$/ if arch == 'x86_64' arch = 'amd64' end version = version[0,2] + '.' + version[2,2] if (variant =~ /ubuntu/ && !version.include?(".")) release_file = "/repos/apt/#{codename}/pool/#{puppet_collection}/p/puppet-agent/puppet-agent*#{arch}.deb" download_file = "puppet-agent-#{variant}-#{version}-#{arch}.tar.gz" when /^solaris$/ if arch == 'x86_64' arch = 'i386' end release_file = "/repos/solaris/#{version}/#{puppet_collection}/" download_file = "puppet-agent-#{variant}-#{version}-#{arch}.tar.gz" else raise "No pe-promoted installation step for #{variant} yet..." end return '', release_file, download_file end # Installs a given PE promoted package on a host # # @param [String] onhost_copy_base Base copy directory on the host # @param [String] onhost_copied_download Downloaded file path on the host # @param [String] onhost_copied_file Copied file path once un-compressed # @param [String] download_file File name of the downloaded file # @param [Hash{Symbol=>String}] opts additional options # # @return nil def pe_puppet_agent_promoted_package_install( onhost_copy_base, onhost_copied_download, onhost_copied_file, download_file, opts ) uncompress_local_tarball( onhost_copied_download, onhost_copy_base, download_file ) if self['platform'] =~ /^solaris/ # above uncompresses the install from .tar.gz -> .p5p into the # onhost_copied_file directory w/a weird name. We have to read that file # name from the filesystem, so that we can provide it to install_local... pkg_filename = execute( "ls #{onhost_copied_file}" ) onhost_copied_file = "#{onhost_copied_file}#{pkg_filename}" end install_local_package( onhost_copied_file, onhost_copy_base ) nil end # Installs a package already located on a SUT # # @param [String] onhost_package_file Path to the package file to install # @param [String] onhost_copy_dir Path to the directory where the package # file is located. Used on solaris only # # @return nil def install_local_package(onhost_package_file, onhost_copy_dir = nil) variant, version, arch, codename = self['platform'].to_array case variant when /^(fedora|el|redhat|centos)$/ command_name = 'yum' command_name = 'dnf' if variant == 'fedora' && version.to_i > 21 execute("#{command_name} --nogpgcheck localinstall -y #{onhost_package_file}") when /^(opensuse|sles)$/ execute("zypper --non-interactive --no-gpg-checks in #{onhost_package_file}") when /^(debian|ubuntu|cumulus)$/ execute("dpkg -i --force-all #{onhost_package_file}") execute("apt-get update") when /^solaris$/ self.solaris_install_local_package( onhost_package_file, onhost_copy_dir ) when /^osx$/ install_package( onhost_package_file ) else msg = "Platform #{variant} is not supported by the method " msg << 'install_local_package' raise ArgumentError, msg end end # Uncompresses a tarball on the SUT # # @param [String] onhost_tar_file Path to the tarball to uncompress # @param [String] onhost_base_dir Path to the directory to uncompress to # @param [String] download_file Name of the file after uncompressing # # @return nil def uncompress_local_tarball(onhost_tar_file, onhost_base_dir, download_file) variant, version, arch, codename = self['platform'].to_array case variant when /^(fedora|el|centos|redhat|opensuse|sles|debian|ubuntu|cumulus)$/ execute("tar -zxvf #{onhost_tar_file} -C #{onhost_base_dir}") when /^solaris$/ # uncompress PE puppet-agent tarball if version == '10' execute("gunzip #{onhost_tar_file}") tar_file_name = File.basename(download_file, '.gz') execute("tar -xvf #{tar_file_name}") elsif version == '11' execute("tar -zxvf #{onhost_tar_file}") else msg = "Solaris #{version} is not supported by the method " msg << 'uncompress_local_tarball' raise ArgumentError, msg end else msg = "Platform #{variant} is not supported by the method " msg << 'uncompress_local_tarball' raise ArgumentError, msg end end # Installs a local package file on a solaris host # # @param [String] package_path Path to the package file on the host # @param [String] noask_directory Path to the directory for the noask file # (only needed for solaris 10). # # @return [Beaker::Result] Result of installation command execution def solaris_install_local_package(package_path, noask_directory = nil) variant, version, arch, codename = self['platform'].to_array version = version.split('.')[0] # packages are only published for major versions error_message = nil unless variant == 'solaris' error_message = "Can not call solaris_install_local_package for the " error_message << "non-solaris platform '#{variant}'" end if version != '10' && version != '11' error_message = "Solaris #{version} is not supported by the method " error_message << 'solaris_install_local_package' end raise ArgumentError, error_message if error_message if version == '10' noask_text = self.noask_file_text create_remote_file self, File.join(noask_directory, 'noask'), noask_text install_cmd = "gunzip -c #{package_path} | pkgadd -d /dev/stdin -a noask -n all" elsif version == '11' install_cmd = "pkg install -g #{package_path} puppet-agent" end self.exec(Beaker::Command.new(install_cmd)) end end beaker-4.30.0/lib/beaker/host/unix/user.rb000066400000000000000000000013761407603575700203160ustar00rootroot00000000000000module Unix::User include Beaker::CommandFactory def user_list(&block) execute("getent passwd") do |result| users = [] result.stdout.each_line do |line| users << (line.match( /^([^:]+)/) or next)[1] end yield result if block_given? users end end def user_get(name, &block) execute("getent passwd #{name}") do |result| fail_test "failed to get user #{name}" unless result.stdout =~ /^#{name}:/ yield result if block_given? result end end def user_present(name, &block) execute("if ! getent passwd #{name}; then useradd #{name}; fi", {}, &block) end def user_absent(name, &block) execute("if getent passwd #{name}; then userdel #{name}; fi", {}, &block) end end beaker-4.30.0/lib/beaker/host/windows.rb000066400000000000000000000030471407603575700200440ustar00rootroot00000000000000[ 'host', 'command_factory', 'command', 'options' ].each do |lib| require "beaker/#{lib}" end module Windows # A windows host with cygwin tools installed class Host < Unix::Host [ 'user', 'group', 'exec', 'pkg', 'file' ].each do |lib| require "beaker/host/windows/#{lib}" end include Windows::User include Windows::Group include Windows::File include Windows::Exec include Windows::Pkg def platform_defaults h = Beaker::Options::OptionsHash.new h.merge({ 'user' => 'Administrator', 'group' => 'Administrators', 'pathseparator' => ';', }) end def external_copy_base return @external_copy_base if @external_copy_base @external_copy_base = execute('echo `cygpath -smF 35`/') @external_copy_base end # Determines which SSH Server is in use on this host # # @return [Symbol] Value for the SSH Server in use # (:bitvise or :openssh at this point). def determine_ssh_server return @ssh_server if @ssh_server @ssh_server = :openssh status = execute('cmd.exe /c sc query BvSshServer', :accept_all_exit_codes => true) @ssh_server = :bitvise if status =~ /4 RUNNING/ logger.debug("windows.rb:determine_ssh_server: determined ssh server: '#{@ssh_server}'") @ssh_server end attr_reader :scp_separator def initialize name, host_hash, options super @ssh_server = nil @scp_separator = '\\' @external_copy_base = nil end end end beaker-4.30.0/lib/beaker/host/windows/000077500000000000000000000000001407603575700175135ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/host/windows/exec.rb000066400000000000000000000114751407603575700207740ustar00rootroot00000000000000module Windows::Exec include Beaker::CommandFactory def reboot exec(Beaker::Command.new('shutdown /f /r /t 0 /d p:4:1 /c "Beaker::Host reboot command issued"'), :reset_connection => true) # rebooting on windows is sloooooow # give it some breathing room before attempting a reconnect sleep(40) end ABS_CMD = 'c:\\\\windows\\\\system32\\\\cmd.exe' CMD = 'cmd.exe' def echo(msg, abs=true) (abs ? ABS_CMD : CMD) + " /c echo #{msg}" end def touch(file, abs=true) (abs ? ABS_CMD : CMD) + " /c echo. 2> #{file}" end def path 'c:/windows/system32;c:/windows' end def get_ip # when querying for an IP this way the return value can be formatted like: # IPAddress= # IPAddress={"129.168.0.1"} # IPAddress={"192.168.0.1","2001:db8:aaaa:bbbb:cccc:dddd:eeee:0001"} ips = execute("wmic nicconfig where ipenabled=true GET IPAddress /format:list") ip = '' ips.each_line do |line| matches = line.split('=') next if matches.length <= 1 matches = matches[1].match(/^{"(.*?)"/) next if matches.nil? || matches.captures.nil? || matches.captures.empty? ip = matches.captures[0] if matches && matches.captures break if ip != '' end ip end # Attempt to ping the provided target hostname # @param [String] target The hostname to ping # @param [Integer] attempts Amount of times to attempt ping before giving up # @return [Boolean] true of ping successful, overwise false def ping target, attempts=5 try = 0 while try < attempts do result = exec(Beaker::Command.new("ping -n 1 #{target}"), :accept_all_exit_codes => true) if result.exit_code == 0 return true end try+=1 end result.exit_code == 0 end # Restarts the SSH service. # # @return [Result] result of starting SSH service def ssh_service_restart command_result = nil # we get periodic failures to restart the service, so looping these with re-attempts repeat_fibonacci_style_for(5) do 0 == exec(Beaker::Command.new("cygrunsrv -E sshd"), :acceptable_exit_codes => [0, 1] ).exit_code end repeat_fibonacci_style_for(5) do command_result = exec(Beaker::Command.new("cygrunsrv -S sshd"), :acceptable_exit_codes => [0, 1] ) 0 == command_result.exit_code end command_result end # Sets the PermitUserEnvironment setting & restarts the SSH service # # @api private # @return [Result] result of the command starting the SSH service # (from {#ssh_service_restart}). def ssh_permit_user_environment exec(Beaker::Command.new("echo '\nPermitUserEnvironment yes' >> /etc/sshd_config")) ssh_service_restart() end # Gets the specific prepend commands as needed for this host # # @param [String] command Command to be executed # @param [String] user_pc List of user-specified commands to prepend # @param [Hash] opts optional parameters # @option opts [Boolean] :cmd_exe whether cmd.exe should be used # # @return [String] Command string as needed for this host def prepend_commands(command = '', user_pc = nil, opts = {}) cygwin_prefix = (self.is_cygwin? and opts[:cmd_exe]) ? 'cmd.exe /c' : '' spacing = (user_pc && !cygwin_prefix.empty?) ? ' ' : '' "#{cygwin_prefix}#{spacing}#{user_pc}" end # Gets the specific append commands as needed for this host # # @param [String] command Command to be executed # @param [String] user_ac List of user-specified commands to append # @param [Hash] opts optional parameters # # @return [String] Command string as needed for this host def append_commands(command = '', user_ac = '', opts = {}) user_ac end # Checks if selinux is enabled # selinux is not available on Windows # # @return [Boolean] false def selinux_enabled?() false end # Create the provided directory structure on the host # @param [String] dir The directory structure to create on the host # @return [Boolean] True, if directory construction succeeded, otherwise False def mkdir_p dir cmd = "mkdir -p \"#{dir}\"" result = exec(Beaker::Command.new(cmd), :acceptable_exit_codes => [0, 1]) result.exit_code == 0 end # Move the origin to destination. The destination is removed prior to moving. # @param [String] orig The origin path # @param [String] dest the destination path # @param [Boolean] rm Remove the destination prior to move def mv orig, dest, rm=true rm_rf dest unless !rm execute("mv \"#{orig}\" \"#{dest}\"") end # Determine if cygwin is actually installed on the SUT. Differs from # is_cygwin?, which is just a type check for a Windows::Host. # # @return [Boolean] def cygwin_installed? output = exec(Beaker::Command.new('cygcheck --check-setup cygwin'), :accept_all_exit_codes => true).stdout return true if output.match(/cygwin/) && output.match(/OK/) false end end beaker-4.30.0/lib/beaker/host/windows/file.rb000066400000000000000000000045501407603575700207630ustar00rootroot00000000000000module Windows::File include Beaker::CommandFactory def tmpfile(name = '') execute("cygpath -m $(mktemp -t #{name}.XXXXXX)") end def tmpdir(name = '') execute("cygpath -m $(mktemp -td #{name}.XXXXXX)") end def system_temp_path # under CYGWIN %TEMP% may not be set tmp_path = execute('ECHO %SYSTEMROOT%', :cmdexe => true) tmp_path.gsub(/\n/, '') + '\\TEMP' end # (see {Beaker::Host::Unix::File#chown}) # @note Cygwin's `chown` implementation does not support # windows-, DOS-, or mixed-style paths, only UNIX/POSIX-style. # This method simply wraps the normal Host#chown call with # a call to cygpath to sanitize input. def chown(user, path, recursive=false) cygpath = execute("cygpath -u #{path}") super(user, cygpath, recursive) end # (see {Beaker::Host::Unix::File#chgrp}) # @note Cygwin's `chgrp` implementation does not support # windows-, DOS-, or mixed-style paths, only UNIX/POSIX-style. # This method simply wraps the normal Host#chgrp call with # a call to cygpath to sanitize input. def chgrp(group, path, recursive=false) cygpath = execute("cygpath -u #{path}") super(group, cygpath, recursive) end # Not needed on windows def chmod(mod, path, recursive=false); end # (see {Beaker::Host::Unix::File#ls_ld}) # @note Cygwin's `ls_ld` implementation does not support # windows-, DOS-, or mixed-style paths, only UNIX/POSIX-style. # This method simply wraps the normal Host#ls_ld call with # a call to cygpath to sanitize input. def ls_ld(path) cygpath = execute("cygpath -u #{path}") super(cygpath) end # Updates a file path for use with SCP, depending on the SSH Server # # @note This will fail with an SSH server that is not OpenSSL or BitVise. # # @param [String] path Path to be changed # # @return [String] Path updated for use by SCP def scp_path(path) case determine_ssh_server when :bitvise # swap out separators network_path = path.gsub('\\', scp_separator) when :openssh path else raise ArgumentError("windows/file.rb:scp_path: ssh server not recognized: '#{determine_ssh_server}'") end end def path_split(paths) paths.split(';') end def file_exist?(path) result = exec(Beaker::Command.new("test -e '#{path}'"), :acceptable_exit_codes => [0, 1]) result.exit_code == 0 end end beaker-4.30.0/lib/beaker/host/windows/group.rb000066400000000000000000000017061407603575700212000ustar00rootroot00000000000000module Windows::Group include Beaker::CommandFactory def group_list(&block) execute('cmd /c echo "" | wmic group where localaccount="true" get name /format:value') do |result| groups = [] result.stdout.each_line do |line| groups << (line.match(/^Name=(.+)$/) or next)[1] end yield result if block_given? groups end end def group_get(name, &block) execute("net localgroup \"#{name}\"") do |result| fail_test "failed to get group #{name}" if result.exit_code != 0 yield result if block_given? result end end def group_gid(name) raise NotImplementedError, "Can't retrieve group gid on a Windows host" end def group_present(name, &block) execute("net localgroup /add \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end def group_absent(name, &block) execute("net localgroup /delete \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end end beaker-4.30.0/lib/beaker/host/windows/pkg.rb000066400000000000000000000072651407603575700206330ustar00rootroot00000000000000module Windows::Pkg include Beaker::CommandFactory def check_for_command(name) result = exec(Beaker::Command.new("which #{name}"), :accept_all_exit_codes => true) result.exit_code == 0 end def check_for_package(name) result = exec(Beaker::Command.new("cygcheck #{name}"), :accept_all_exit_codes => true) result.exit_code == 0 end def install_package(name, cmdline_args = '') arch = identify_windows_architecture if arch == '64' rootdir = "c:\\\\cygwin64" cygwin = "setup-x86_64.exe" else #32 bit version rootdir = "c:\\\\cygwin" cygwin = "setup-x86.exe" end execute("#{cygwin} -q -n -N -d -R #{rootdir} -s http://cygwin.osuosl.org -P #{name} #{cmdline_args}") end def uninstall_package(name, cmdline_args = '') raise "Package #{name} cannot be uninstalled on #{self}" end #Examine the host system to determine the architecture, overrides default host determine_if_x86_64 so that wmic is used #@return [Boolean] true if x86_64, false otherwise def determine_if_x86_64 (identify_windows_architecture =~ /64/) == 0 end # Gets the path & file name for the puppet agent dev package on Windows # # @param [String] puppet_collection Name of the puppet collection to use # @param [String] puppet_agent_version Version of puppet agent to get # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @note Windows only uses the 'install_32' option of the opts hash at this # time. Note that it will not fail if not provided, however # # @return [String, String] Path to the directory and filename of the package, respectively def puppet_agent_dev_package_info( puppet_collection = nil, puppet_agent_version = nil, opts = {} ) release_path_end = 'windows' is_config_32 = self['ruby_arch'] == 'x86' || self['install_32'] || opts['install_32'] should_install_64bit = self.is_x86_64? && !is_config_32 # only install 64bit builds if # - we do not have install_32 set on host # - we do not have install_32 set globally arch_suffix = should_install_64bit ? '64' : '86' # If a version was specified, use it; otherwise fall back to a default name. # Avoid when puppet_agent_version is set to a SHA, which isn't used in package names. if puppet_agent_version =~ /^\d+\.\d+\.\d+/ release_file = "puppet-agent-#{puppet_agent_version}-x#{arch_suffix}.msi" else release_file = "puppet-agent-x#{arch_suffix}.msi" end return release_path_end, release_file end # Gets host-specific information for PE promoted puppet-agent packages # # @param [String] puppet_collection Name of the puppet collection to use # @param [Hash{Symbol=>String}] opts Options hash to provide extra values # # @return [String, String, String] Host-specific information for packages # 1. release_path_end Suffix for the release_path # 2. release_file Path to the file on release build servers # 3. download_file Filename for the package itself def pe_puppet_agent_promoted_package_info( puppet_collection = nil, opts = {} ) is_config_32 = self['ruby_arch'] == 'x86' || self['install_32'] || self['install_32'] should_install_64bit = self.is_x86_64? && !is_config_32 # only install 64bit builds if # - we do not have install_32 set on host # - we do not have install_32 set globally arch_suffix = should_install_64bit ? '64' : '86' release_path_end = "/windows" release_file = "/puppet-agent-x#{arch_suffix}.msi" download_file = "puppet-agent-x#{arch_suffix}.msi" return release_path_end, release_file, download_file end private # @api private def identify_windows_architecture platform.arch =~ /64/ ? '64' : '32' end end beaker-4.30.0/lib/beaker/host/windows/user.rb000066400000000000000000000015071407603575700210210ustar00rootroot00000000000000module Windows::User include Beaker::CommandFactory def user_list(&block) execute('cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value') do |result| users = [] result.stdout.each_line do |line| users << (line.match(/^Name=(.+)/) or next)[1] end yield result if block_given? users end end def user_get(name, &block) execute("net user \"#{name}\"") do |result| fail_test "failed to get user #{name}" if result.exit_code != 0 yield result if block_given? result end end def user_present(name, &block) execute("net user /add \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end def user_absent(name, &block) execute("net user /delete \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block) end end beaker-4.30.0/lib/beaker/host_prebuilt_steps.rb000066400000000000000000000676571407603575700215170ustar00rootroot00000000000000require 'pathname' [ 'command', "dsl" ].each do |lib| require "beaker/#{lib}" end module Beaker #Provides convienience methods for commonly run actions on hosts module HostPrebuiltSteps include Beaker::DSL::Patterns NTPSERVER = 'pool.ntp.org' SLEEPWAIT = 5 TRIES = 5 RHEL8_PACKAGES = ['curl', 'chrony'] FEDORA_PACKAGES = ['curl', 'chrony'] UNIX_PACKAGES = ['curl', 'ntpdate'] FREEBSD_PACKAGES = ['curl', 'perl5|perl'] OPENBSD_PACKAGES = ['curl'] ARCHLINUX_PACKAGES = ['curl', 'ntp'] WINDOWS_PACKAGES = ['curl'] PSWINDOWS_PACKAGES = [] SLES10_PACKAGES = ['curl'] SLES_PACKAGES = ['curl', 'ntp'] DEBIAN_PACKAGES = ['curl', 'ntpdate', 'lsb-release', 'apt-transport-https'] CUMULUS_PACKAGES = ['curl', 'ntpdate'] SOLARIS10_PACKAGES = ['CSWcurl', 'CSWntp', 'wget'] SOLARIS11_PACKAGES = ['curl', 'ntp'] ETC_HOSTS_PATH = "/etc/hosts" ETC_HOSTS_PATH_SOLARIS = "/etc/inet/hosts" ROOT_KEYS_SCRIPT = "https://raw.githubusercontent.com/puppetlabs/puppetlabs-sshkeys/master/templates/scripts/manage_root_authorized_keys" ROOT_KEYS_SYNC_CMD = "curl -k -o - -L #{ROOT_KEYS_SCRIPT} | %s" ROOT_KEYS_SYNC_CMD_AIX = "curl --tlsv1 -o - -L #{ROOT_KEYS_SCRIPT} | %s" APT_CFG = %q{ Acquire::http::Proxy "http://proxy.puppetlabs.net:3128/"; } IPS_PKG_REPO="http://solaris-11-internal-repo.delivery.puppetlabs.net" #Run timesync on the provided hosts # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def timesync host, opts logger = opts[:logger] ntp_server = opts[:ntp_server] ? opts[:ntp_server] : NTPSERVER block_on host do |host| logger.notify "Update system time sync for '#{host.name}'" if host['platform'].include? 'windows' # The exit code of 5 is for Windows 2008 systems where the w32tm /register command # is not actually necessary. host.exec(Command.new("w32tm /register"), :acceptable_exit_codes => [0,5]) host.exec(Command.new("net start w32time"), :acceptable_exit_codes => [0,2]) host.exec(Command.new("w32tm /config /manualpeerlist:#{ntp_server} /syncfromflags:manual /update")) host.exec(Command.new("w32tm /resync")) logger.notify "NTP date succeeded on #{host}" else case when host['platform'] =~ /el-8|fedora/ ntp_command = "chronyc add server #{ntp_server} prefer trust;chronyc makestep;chronyc burst 1/2" when host['platform'] =~ /opensuse-|sles-/ ntp_command = "sntp #{ntp_server}" when host['platform'] =~ /cisco_nexus/ ntp_server = host.exec(Command.new("getent hosts #{NTPSERVER} | head -n1 |cut -d \" \" -f1"), :acceptable_exit_codes => [0]).stdout ntp_command = "sudo -E sh -c 'export DCOS_CONTEXT=2;/isan/bin/ntpdate -u -t 20 #{ntp_server}'" else ntp_command = "ntpdate -u -t 20 #{ntp_server}" end success=false try = 0 until try >= TRIES do try += 1 if host.exec(Command.new(ntp_command), :accept_all_exit_codes => true).exit_code == 0 success=true break end sleep SLEEPWAIT end if success logger.notify "NTP date succeeded on #{host} after #{try} tries" else raise "NTP date was not successful after #{try} tries" end end end nil rescue => e report_and_raise(logger, e, "timesync (--ntp)") end # Validate that hosts are prepared to be used as SUTs, if packages are missing attempt to # install them. # # Verifies the presence of #{HostPrebuiltSteps::UNIX_PACKAGES} on unix platform hosts, # {HostPrebuiltSteps::SLES_PACKAGES} on SUSE platform hosts, # {HostPrebuiltSteps::DEBIAN_PACKAGES} on debian platform hosts, # {HostPrebuiltSteps::CUMULUS_PACKAGES} on cumulus platform hosts, # {HostPrebuiltSteps::WINDOWS_PACKAGES} on cygwin-installed windows platform hosts, # and {HostPrebuiltSteps::PSWINDOWS_PACKAGES} on non-cygwin windows platform hosts. # # @param [Host, Array, String, Symbol] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def validate_host host, opts logger = opts[:logger] block_on host do |host| case when host['platform'] =~ /el-8/ check_and_install_packages_if_needed(host, RHEL8_PACKAGES) when host['platform'] =~ /sles-10/ check_and_install_packages_if_needed(host, SLES10_PACKAGES) when host['platform'] =~ /opensuse|sles-/ check_and_install_packages_if_needed(host, SLES_PACKAGES) when host['platform'] =~ /debian/ check_and_install_packages_if_needed(host, DEBIAN_PACKAGES) when host['platform'] =~ /cumulus/ check_and_install_packages_if_needed(host, CUMULUS_PACKAGES) when (host['platform'] =~ /windows/ and host.is_cygwin?) raise RuntimeError, "cygwin is not installed on #{host}" if !host.cygwin_installed? check_and_install_packages_if_needed(host, WINDOWS_PACKAGES) when (host['platform'] =~ /windows/ and not host.is_cygwin?) check_and_install_packages_if_needed(host, PSWINDOWS_PACKAGES) when host['platform'] =~ /freebsd/ check_and_install_packages_if_needed(host, FREEBSD_PACKAGES) when host['platform'] =~ /openbsd/ check_and_install_packages_if_needed(host, OPENBSD_PACKAGES) when host['platform'] =~ /solaris-10/ check_and_install_packages_if_needed(host, SOLARIS10_PACKAGES) when host['platform'] =~ /solaris-1[1-9]/ check_and_install_packages_if_needed(host, SOLARIS11_PACKAGES) when host['platform'] =~ /archlinux/ check_and_install_packages_if_needed(host, ARCHLINUX_PACKAGES) when host['platform'] =~ /fedora/ check_and_install_packages_if_needed(host, FEDORA_PACKAGES) when host['platform'] !~ /debian|aix|solaris|windows|opensuse-|sles-|osx-|cumulus|f5-|netscaler|cisco_/ check_and_install_packages_if_needed(host, UNIX_PACKAGES) end end rescue => e report_and_raise(logger, e, "validate") end # Installs the given packages if they aren't already on a host # # @param [Host] host Host to act on # @param [Array] package_list List of package names to install def check_and_install_packages_if_needed host, package_list package_list.each do |string| alternatives = string.split('|') next if alternatives.any? { |pkg| host.check_for_package pkg } install_one_of_packages host, alternatives end end # Installs one of alternative packages (first available) # # @param [Host] host Host to act on # @param [Array] packages List of package names (alternatives). def install_one_of_packages host, packages error = nil packages.each do |pkg| begin return host.install_package pkg rescue Beaker::Host::CommandFailure => e error = e end end raise error end #Install a set of authorized keys using {HostPrebuiltSteps::ROOT_KEYS_SCRIPT}. This is a #convenience method to allow for easy login to hosts after they have been provisioned with #Beaker. # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def sync_root_keys host, opts # JJM This step runs on every system under test right now. We're anticipating # issues on Windows and maybe Solaris. We will likely need to filter this step # but we're deliberately taking the approach of "assume it will work, fix it # when reality dictates otherwise" logger = opts[:logger] block_on host do |host| logger.notify "Sync root authorized_keys from github on #{host.name}" # Allow all exit code, as this operation is unlikely to cause problems if it fails. if host['platform'] =~ /solaris|eos/ host.exec(Command.new(ROOT_KEYS_SYNC_CMD % "bash"), :accept_all_exit_codes => true) elsif host['platform'] =~ /aix/ host.exec(Command.new(ROOT_KEYS_SYNC_CMD_AIX % "env PATH=/usr/gnu/bin:$PATH bash"), :accept_all_exit_codes => true) else host.exec(Command.new(ROOT_KEYS_SYNC_CMD % "env PATH=\"/usr/gnu/bin:$PATH\" bash"), :accept_all_exit_codes => true) end end rescue => e report_and_raise(logger, e, "sync_root_keys") end # Run 'apt-get update' on the provided host or hosts. # If the platform of the provided host is not ubuntu, debian or cumulus: do nothing. # # @param [Host, Array] hosts One or more hosts to act upon def apt_get_update hosts block_on hosts do |host| if host[:platform] =~ /ubuntu|debian|cumulus/ host.exec(Command.new("apt-get update")) end end end #Create a file on host or hosts at the provided file path with the provided file contents. # @param [Host, Array] host One or more hosts to act upon # @param [String] file_path The path at which the new file will be created on the host or hosts. # @param [String] file_content The contents of the file to be created on the host or hosts. def copy_file_to_remote(host, file_path, file_content) block_on host do |host| Tempfile.open 'beaker' do |tempfile| File.open(tempfile.path, 'w') {|file| file.puts file_content } host.do_scp_to(tempfile.path, file_path, @options) end end end # On ubuntu, debian, or cumulus host or hosts: alter apt configuration to use # the internal Puppet Labs proxy {HostPrebuiltSteps::APT_CFG} proxy. # On solaris-11 host or hosts: alter pkg to point to # the internal Puppet Labs proxy {HostPrebuiltSteps::IPS_PKG_REPO}. # # Do nothing for other platform host or hosts. # # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def proxy_config( host, opts ) logger = opts[:logger] block_on host do |host| case when host['platform'] =~ /ubuntu|debian|cumulus/ host.exec(Command.new("if test -f /etc/apt/apt.conf; then mv /etc/apt/apt.conf /etc/apt/apt.conf.bk; fi")) copy_file_to_remote(host, '/etc/apt/apt.conf', APT_CFG) apt_get_update(host) when host['platform'] =~ /solaris-11/ host.exec(Command.new("/usr/bin/pkg unset-publisher solaris || :")) host.exec(Command.new("/usr/bin/pkg set-publisher -g %s solaris" % IPS_PKG_REPO)) else logger.debug "#{host}: repo proxy configuration not modified" end end rescue => e report_and_raise(logger, e, "proxy_config") end #Install EPEL on host or hosts with platform = /el-(6|7)/. Do nothing on host or hosts of other platforms. # @param [Host, Array] host One or more hosts to act upon. Will use individual host epel_url, epel_arch # and epel_pkg before using defaults provided in opts. # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Boolean] :debug If true, print verbose rpm information when installing EPEL # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object # @option opts [String] :epel_url Link to download from def add_el_extras( host, opts ) #add_el_extras #only supports el-* platforms logger = opts[:logger] debug_opt = opts[:debug] ? 'vh' : '' block_on host do |host| case when el_based?(host) && ['6','7'].include?(host['platform'].version) result = host.exec(Command.new('rpm -qa | grep epel-release'), :acceptable_exit_codes => [0,1]) if result.exit_code == 1 url_base = opts[:epel_url] host.install_package_with_rpm("#{url_base}/epel-release-latest-#{host['platform'].version}.noarch.rpm", '--replacepkgs', { :package_proxy => opts[:package_proxy] }) #update /etc/yum.repos.d/epel.repo for new baseurl host.exec(Command.new("sed -i -e 's;#baseurl.*$;baseurl=#{Regexp.escape("#{url_base}/#{host['platform'].version}")}/\$basearch;' /etc/yum.repos.d/epel.repo")) #remove mirrorlist host.exec(Command.new("sed -i -e '/mirrorlist/d' /etc/yum.repos.d/epel.repo")) host.exec(Command.new('yum clean all && yum makecache')) end else logger.debug "#{host}: package repo configuration not modified" end end rescue => e report_and_raise(logger, e, "add_repos") end #Determine the domain name of the provided host from its /etc/resolv.conf # @param [Host] host the host to act upon def get_domain_name(host) domain = nil search = nil if host['platform'] =~ /windows/ if host.is_cygwin? resolv_conf = host.exec(Command.new("cat /cygdrive/c/Windows/System32/drivers/etc/hosts")).stdout else resolv_conf = host.exec(Command.new('type C:\Windows\System32\drivers\etc\hosts')).stdout end else resolv_conf = host.exec(Command.new("cat /etc/resolv.conf")).stdout end resolv_conf.each_line { |line| if line =~ /^\s*domain\s+(\S+)/ domain = $1 elsif line =~ /^\s*search\s+(\S+)/ search = $1 end } return_value ||= domain return_value ||= search if return_value return_value.gsub(/\.$/, '') end end #Determine the ip address of the provided host # @param [Host] host the host to act upon # @deprecated use {Host#get_ip} def get_ip(host) host.get_ip end #Append the provided string to the /etc/hosts file of the provided host # @param [Host] host the host to act upon # @param [String] etc_hosts The string to append to the /etc/hosts file def set_etc_hosts(host, etc_hosts) if host['platform'] =~ /freebsd/ host.echo_to_file(etc_hosts, '/etc/hosts') elsif ((host['platform'] =~ /windows/) and not host.is_cygwin?) host.exec(Command.new("echo '#{etc_hosts}' >> C:\\Windows\\System32\\drivers\\etc\\hosts")) else host.exec(Command.new("echo '#{etc_hosts}' >> /etc/hosts")) end # AIX must be configured to prefer local DNS over external if host['platform'] =~ /aix/ aix_netsvc = '/etc/netsvc.conf' aix_local_resolv = 'hosts = local, bind' unless host.exec(Command.new("grep '#{aix_local_resolv}' #{aix_netsvc}"), :accept_all_exit_codes => true).exit_code == 0 host.exec(Command.new("echo '#{aix_local_resolv}' >> #{aix_netsvc}")) end end end #Make it possible to log in as root by copying the current users ssh keys to the root account # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def copy_ssh_to_root host, opts logger = opts[:logger] block_on host do |host| logger.debug "Give root a copy of current user's keys, on #{host.name}" if host['platform'] =~ /windows/ and host.is_cygwin? host.exec(Command.new('cp -r .ssh /cygdrive/c/Users/Administrator/.')) host.exec(Command.new('chown -R Administrator /cygdrive/c/Users/Administrator/.ssh')) elsif host['platform'] =~ /windows/ and not host.is_cygwin? # from https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/xcopy.mspx?mfr=true: # /i : If Source is a directory or contains wildcards and Destination # does not exist, xcopy assumes destination specifies a directory # name and creates a new directory. Then, xcopy copies all specified # files into the new directory. By default, xcopy prompts you to # specify whether Destination is a file or a directory. # # /y : Suppresses prompting to confirm that you want to overwrite an # existing destination file. host.exec(Command.new("if exist .ssh (xcopy .ssh C:\\Users\\Administrator\\.ssh /s /e /y /i)")) elsif host['platform'] =~ /osx/ host.exec(Command.new('sudo cp -r .ssh /var/root/.'), {:pty => true}) elsif host['platform'] =~ /freebsd/ host.exec(Command.new('sudo cp -r .ssh /root/.'), {:pty => true}) elsif host['platform'] =~ /openbsd/ host.exec(Command.new('sudo cp -r .ssh /root/.'), {:pty => true}) elsif host['platform'] =~ /solaris-10/ host.exec(Command.new('sudo cp -r .ssh /.'), {:pty => true}) elsif host['platform'] =~ /solaris-11/ host.exec(Command.new('sudo cp -r .ssh /root/.'), {:pty => true}) else host.exec(Command.new('sudo su -c "cp -r .ssh /root/."'), {:pty => true}) end if host.selinux_enabled? host.exec(Command.new('sudo fixfiles restore /root')) end end end # Update /etc/hosts to make it possible for each provided host to reach each other host by name. # Assumes that each provided host has host[:ip] set; in the instance where a provider sets # host['ip'] to an address which facilitates access to the host externally, but the actual host # addresses differ from this, we check first for the presence of a host['vm_ip'] key first, # and use that if present. # @param [Host, Array] hosts An array of hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def hack_etc_hosts hosts, opts etc_hosts = "127.0.0.1\tlocalhost localhost.localdomain\n" hosts.each do |host| ip = host['vm_ip'] || host['ip'].to_s hostname = host[:vmhostname] || host.name domain = get_domain_name(host) etc_hosts += "#{ip}\t#{hostname}.#{domain} #{hostname}\n" end hosts.each do |host| set_etc_hosts(host, etc_hosts) end end # Update /etc/hosts to make updates.puppetlabs.com (aka the dujour server) resolve to 127.0.01, # so that we don't pollute the server with test data. See SERVER-1000, BKR-182, BKR-237, DJ-10 # for additional details. def disable_updates hosts, opts logger = opts[:logger] hosts.each do |host| next if host['platform'] =~ /netscaler/ logger.notify "Disabling updates.puppetlabs.com by modifying hosts file to resolve updates to 127.0.0.1 on #{host}" set_etc_hosts(host, "127.0.0.1\tupdates.puppetlabs.com\n") end end # Update sshd_config on debian, ubuntu, centos, el, redhat, cumulus, and fedora boxes to allow for root login # # Does nothing on other platfoms. # # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def enable_root_login host, opts logger = opts[:logger] block_on host do |host| logger.debug "Update sshd_config to allow root login" if host['platform'] =~ /osx/ # If osx > 10.10 use '/private/etc/ssh/sshd_config', else use '/etc/sshd_config' ssh_config_file = '/private/etc/ssh/sshd_config' ssh_config_file = '/etc/sshd_config' if host['platform'] =~ /^osx-10\.(9|10)/i host.exec(Command.new("sudo sed -i '' 's/#PermitRootLogin no/PermitRootLogin Yes/g' #{ssh_config_file}")) host.exec(Command.new("sudo sed -i '' 's/#PermitRootLogin yes/PermitRootLogin Yes/g' #{ssh_config_file}")) elsif host['platform'] =~ /freebsd/ host.exec(Command.new("sudo sed -i -e 's/#PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config"), {:pty => true} ) elsif host['platform'] =~ /openbsd/ host.exec(Command.new("sudo perl -pi -e 's/^PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config"), {:pty => true} ) elsif host['platform'] =~ /solaris-10/ host.exec(Command.new("sudo gsed -i -e 's/#PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config"), {:pty => true} ) elsif host['platform'] =~ /solaris-11/ host.exec(Command.new("if grep \"root::::type=role\" /etc/user_attr; then sudo rolemod -K type=normal root; else echo \"root user already type=normal\"; fi"), {:pty => true} ) host.exec(Command.new("sudo gsed -i -e 's/PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config"), {:pty => true} ) elsif host['platform'] =~ /f5/ #interacting with f5 should using tmsh logger.warn("Attempting to enable root login non-supported platform: #{host.name}: #{host['platform']}") elsif host.is_cygwin? host.exec(Command.new("sed -ri 's/^#?PermitRootLogin /PermitRootLogin yes/' /etc/sshd_config"), {:pty => true}) elsif host.is_powershell? logger.warn("Attempting to enable root login non-supported platform: #{host.name}: #{host['platform']}") else host.exec(Command.new("sudo su -c \"sed -ri 's/^#?PermitRootLogin no|^#?PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config\""), {:pty => true}) end #restart sshd if host['platform'] =~ /debian|ubuntu|cumulus/ host.exec(Command.new("sudo su -c \"service ssh restart\""), {:pty => true}) elsif host['platform'] =~ /arch|centos-7|el-7|redhat-7|centos-8|el-8|redhat-8|fedora-(1[4-9]|2[0-9]|3[0-9])/ host.exec(Command.new("sudo -E systemctl restart sshd.service"), {:pty => true}) elsif host['platform'] =~ /centos|el-|redhat|fedora|eos/ host.exec(Command.new("sudo -E /sbin/service sshd reload"), {:pty => true}) elsif host['platform'] =~ /(free|open)bsd/ host.exec(Command.new("sudo /etc/rc.d/sshd restart")) elsif host['platform'] =~ /solaris/ host.exec(Command.new("sudo -E svcadm restart network/ssh"), {:pty => true} ) else logger.warn("Attempting to update ssh on non-supported platform: #{host.name}: #{host['platform']}") end end end #Disable SELinux on centos, does nothing on other platforms # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def disable_se_linux host, opts logger = opts[:logger] block_on host do |host| if host['platform'] =~ /centos|el-|redhat|fedora|eos/ @logger.debug("Disabling se_linux on #{host.name}") host.exec(Command.new("sudo su -c \"setenforce 0\""), {:pty => true}) else @logger.warn("Attempting to disable SELinux on non-supported platform: #{host.name}: #{host['platform']}") end end end #Disable iptables on centos, does nothing on other platforms # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def disable_iptables host, opts logger = opts[:logger] block_on host do |host| if host['platform'] =~ /centos|el-|redhat|fedora|eos/ logger.debug("Disabling iptables on #{host.name}") host.exec(Command.new("sudo su -c \"/etc/init.d/iptables stop\""), {:pty => true}) else logger.warn("Attempting to disable iptables on non-supported platform: #{host.name}: #{host['platform']}") end end end # Setup files for enabling requests to pass to a proxy server # This works for the APT package manager on debian, ubuntu, and cumulus # and YUM package manager on el, centos, fedora and redhat. # @param [Host, Array, String, Symbol] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. # @option opts [Beaker::Logger] :logger A {Beaker::Logger} object def package_proxy host, opts logger = opts[:logger] block_on host do |host| logger.debug("enabling proxy support on #{host.name}") case host['platform'] when /ubuntu/, /debian/, /cumulus/ host.exec(Command.new("echo 'Acquire::http::Proxy \"#{opts[:package_proxy]}/\";' >> /etc/apt/apt.conf.d/10proxy")) when /^el-/, /centos/, /fedora/, /redhat/, /eos/ host.exec(Command.new("echo 'proxy=#{opts[:package_proxy]}/' >> /etc/yum.conf")) else logger.debug("Attempting to enable package manager proxy support on non-supported platform: #{host.name}: #{host['platform']}") end end end # Merge the two provided hashes so that an array of values is created from collisions # @param [Hash] h1 The first hash # @param [Hash] h2 The second hash # @return [Hash] A merged hash with arrays of values where collisions between the two hashes occured. # @example # > h1 = {:PATH=>"/1st/path"} # > h2 = {:PATH=>"/2nd/path"} # > additive_hash_merge(h1, h2) # => {:PATH=>["/1st/path", "/2nd/path"]} def additive_hash_merge h1, h2 merged_hash = {} normalized_h2 = h2.inject({}) { |h, (k, v)| h[k.to_s.upcase] = v; h } h1.each_pair do |key, val| normalized_key = key.to_s.upcase if normalized_h2.has_key?(normalized_key) merged_hash[key] = [h1[key], normalized_h2[normalized_key]] merged_hash[key] = merged_hash[key].uniq #remove dupes end end merged_hash end # Create the hash of default environment from host (:host_env), global options hash (:host_env) and default PE/Foss puppet variables # @param [Host] host The host to construct the environment hash for, host specific environment should be in :host_env in a hash # @param [Hash] opts Hash of options, including optional global host_env to be applied to each provided host # @return [Hash] A hash of environment variables for provided host def construct_env host, opts env = additive_hash_merge(host[:host_env], opts[:host_env]) env.each_key do |key| separator = host['pathseparator'] if key == 'PATH' && (not host.is_powershell?) separator = ':' end env[key] = env[key].join(separator) end env end # Add a host specific set of env vars to each provided host's ~/.ssh/environment # @param [Host, Array] host One or more hosts to act upon # @param [Hash{Symbol=>String}] opts Options to alter execution. def set_env host, opts logger = opts[:logger] block_on host do |host| skip_msg = host.skip_set_env? unless skip_msg.nil? logger.debug( skip_msg ) next end env = construct_env(host, opts) logger.debug("setting local environment on #{host.name}") if host['platform'] =~ /windows/ && host.is_cygwin? env['CYGWIN'] = 'nodosfilewarning' end host.ssh_permit_user_environment host.ssh_set_user_environment(env) #close the host to re-establish the connection with the new sshd settings host.close # print out the working env if host.is_powershell? host.exec(Command.new("SET")) else host.exec(Command.new("cat #{host[:ssh_env_file]}")) end end end private # A helper to tell whether a host is el-based # @param [Host] host the host to act upon # # @return [Boolean] if the host is el_based def el_based? host ['centos','redhat','scientific','el','oracle'].include?(host['platform'].variant) end end end beaker-4.30.0/lib/beaker/hypervisor.rb000066400000000000000000000111541407603575700176050ustar00rootroot00000000000000[ 'host_prebuilt_steps' ].each do |lib| require "beaker/#{lib}" end module Beaker #The Beaker class that interacts to all the supported hypervisors class Hypervisor include HostPrebuiltSteps #Generates an array with all letters a thru z and numbers 0 thru 9 CHARMAP = ('a'..'z').to_a + ('0'..'9').to_a #Hypervisor creator method. Creates the appropriate hypervisor class object based upon #the provided hypervisor type selected, then provisions hosts with hypervisor. #@param [String] type The type of hypervisor to create - one of aix, solaris, vsphere, fusion, # blimpy, vcloud or vagrant #@param [Array] hosts_to_provision The hosts to be provisioned with the selected hypervisor #@param [Hash] options options Options to alter execution #@option options [String] :host_name_prefix (nil) Prefix host name if set def self.create(type, hosts_to_provision, options) @logger = options[:logger] @logger.notify("Beaker::Hypervisor, found some #{type} boxes to create") hyper_class = case type when /^noop$/ Beaker::Noop when /^(default)|(none)$/ Beaker::Hypervisor else # Custom hypervisor require "beaker/hypervisor/#{type}" Beaker.const_get(type.split('_').collect(&:capitalize).join) end hypervisor = hyper_class.new(hosts_to_provision, options) self.set_ssh_connection_preference(hosts_to_provision, hypervisor) hypervisor.provision if options[:provision] hypervisor end def initialize(hosts, options) @hosts = hosts @options = options end #Provisioning steps for be run for a given hypervisor. Default is nil. def provision nil end #Cleanup steps to be run for a given hypervisor. Default is nil. def cleanup nil end DEFAULT_CONNECTION_PREFERENCE = [:ip, :vmhostname, :hostname] # SSH connection method preference. Can be overwritten by hypervisor to change the order def connection_preference(host) DEFAULT_CONNECTION_PREFERENCE end def self.set_ssh_connection_preference(hosts_to_provision, hypervisor) hosts_to_provision.each do |host| ssh_methods = hypervisor.connection_preference(host) + DEFAULT_CONNECTION_PREFERENCE if host[:ssh_preference] # If user has provided ssh_connection_preference in hosts file then concat the preference provided by hypervisor # Followed by concatenating the default preference and keeping the unique once ssh_methods = host[:ssh_preference] + ssh_methods end host[:ssh_connection_preference] = ssh_methods.uniq end end #Proxy package managers on tests hosts created by this hypervisor, runs before validation and configuration. def proxy_package_manager if @options[:package_proxy] package_proxy(@hosts, @options) end end #Default configuration steps to be run for a given hypervisor. Any additional configuration to be done #to the provided SUT for test execution to be successful. def configure(opts = {}) begin return unless @options[:configure] run_in_parallel = run_in_parallel? opts, @options, 'configure' block_on @hosts, { :run_in_parallel => run_in_parallel} do |host| if host[:timesync] timesync(host, @options) end end if @options[:root_keys] sync_root_keys(@hosts, @options) end if @options[:add_el_extras] add_el_extras(@hosts, @options) end if @options[:disable_iptables] disable_iptables @hosts, @options end if @options[:set_env] set_env(@hosts, @options) end if @options[:disable_updates] disable_updates(@hosts, @options) end rescue SignalException => ex if ex.signo == 15 #SIGTERM report_and_raise(@logger, ex, "configure") end raise end end #Default validation steps to be run for a given hypervisor. Ensures that SUTs meet requirements to be #beaker test nodes. def validate if @options[:validate] validate_host(@hosts, @options) end end #Generate a random string composted of letter and numbers #prefixed with value of {Beaker::Hypervisor::create} option :host_name_prefix def generate_host_name n = CHARMAP[rand(25)] + (0...14).map{CHARMAP[rand(CHARMAP.length)]}.join if @options[:host_name_prefix] return @options[:host_name_prefix] + n end n end end end require "beaker/hypervisor/noop" beaker-4.30.0/lib/beaker/hypervisor/000077500000000000000000000000001407603575700172565ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/hypervisor/noop.rb000066400000000000000000000006171407603575700205620ustar00rootroot00000000000000module Beaker class Noop < Beaker::Hypervisor def initialize(new_hosts, options) @options = options @logger = options[:logger] @hosts = new_hosts end def validate # noop end def configure # noop end def proxy_package_manager # noop end def provision # noop end def cleanup # noop end end end beaker-4.30.0/lib/beaker/junit.xsl000066400000000000000000000363331407603575700167350ustar00rootroot00000000000000
danger success

Elapsed Time: sec

Total:

Failed:

Skipped:

Pending:

beaker-4.30.0/lib/beaker/local_connection.rb000066400000000000000000000040741407603575700207070ustar00rootroot00000000000000require 'open3' module Beaker class LocalConnection attr_accessor :logger, :hostname, :ip def initialize options = {} @logger = options[:logger] @ssh_env_file = File.expand_path(options[:ssh_env_file]) @hostname = 'localhost' @ip = '127.0.0.1' @options = options end def self.connect options = {} connection = new options connection.connect connection end def connect options = {} @logger.debug "Local connection, no connection to start" end def close @logger.debug "Local connection, no connection to close" end def with_env(env) backup = ENV.to_hash ENV.replace(env) yield ensure ENV.replace(backup) end def execute command, options = {}, stdout_callback = nil, stderr_callback = stdout_callback result = Result.new(@hostname, command) envs = {} if File.readable?(@ssh_env_file) File.foreach(@ssh_env_file) do |line| key, value = line.split('=') envs[key] = value end end begin clean_env = ENV.reject{ |k| k =~ /^BUNDLE|^RUBY|^GEM/ } with_env(clean_env) do std_out, std_err, status = Open3.capture3(envs, command) result.stdout << std_out result.stderr << std_err result.exit_code = status.exitstatus end rescue => e result.stderr << e.inspect result.exit_code = 1 end result.finalize! @logger.last_result = result result end def scp_to(source, target, _options = {}) result = Result.new(@hostname, [source, target]) begin FileUtils.cp_r source, target rescue Errno::ENOENT => e @logger.warn "#{e.class} error in cp'ing. Forcing the connection to close, which should " \ "raise an error." end result.stdout << " CP'ed file #{source} to #{target}" result.exit_code = 0 result end def scp_from(source, target, options = {}) scp_to(target, source, options) end end end beaker-4.30.0/lib/beaker/logger.rb000066400000000000000000000404521407603575700166550ustar00rootroot00000000000000module Beaker # The Beaker Logger class # This class handles message reporting for Beaker, it reports based upon a provided log level # to a given destination (be it a string or file) # class Logger #The results of the most recently run command attr_accessor :last_result #Determines the spacing that happens before an output line attr_accessor :line_prefix NORMAL = "\e[00;00m" BRIGHT_NORMAL = "\e[00;01m" BLACK = "\e[00;30m" RED = "\e[00;31m" GREEN = "\e[00;32m" YELLOW = "\e[00;33m" BLUE = "\e[00;34m" MAGENTA = "\e[00;35m" CYAN = "\e[00;36m" WHITE = "\e[00;37m" GREY = "\e[00;00m" # Some terms can't handle grey, use normal BRIGHT_RED = "\e[01;31m" BRIGHT_GREEN = "\e[01;32m" BRIGHT_YELLOW = "\e[01;33m" BRIGHT_BLUE = "\e[01;34m" BRIGHT_MAGENTA = "\e[01;35m" BRIGHT_CYAN = "\e[01;36m" BRIGHT_WHITE = "\e[01;37m" NONE = "" # The defined log levels. Each log level also reports messages at levels lower than itself LOG_LEVELS = { :trace => 6, :debug => 5, :verbose => 3, :info => 2, :notify => 1, :warn => 0, } attr_accessor :color, :log_level, :destinations, :log_colors # Initialization of the Logger class # @overload initialize(dests) # Initialize a Logger object that reports to the provided destinations, use default options # @param [Array] Array of IO and strings (assumed to be file paths) to be reported to # @overload initialize(dests, options) # Initialize a Logger object that reports to the provided destinations, use options from provided option hash # @param [Array] Array of IO and strings (assumed to be file paths) to be reported to # @param [Hash] options Hash of options # @option options [Boolean] :color (true) Print color code before log messages # @option options [Boolean] :quiet (false) Do not log messages to STDOUT # @option options [String] :log_level ("info") Log level (one of "debug" - highest level, "verbose", "info", # "notify" and "warn" - lowest level (see {LOG_LEVELS})) The log level indicates that messages at that # log_level and lower will be reported. def initialize(*args) options = args.last.is_a?(Hash) ? args.pop : {} @color = options[:color] @sublog = nil case options[:log_level] when /trace/i, :trace @log_level = :trace when /debug/i, :debug @log_level = :debug when /verbose/i, :verbose @log_level = :verbose when /info/i, :info @log_level = :info when /notify/i, :notify @log_level = :notify when /warn/i, :warn @log_level = :warn else @log_level = :verbose end @last_result = nil @line_prefix = '' @destinations = [] @log_colors = { :error => RED, :warn => BRIGHT_RED, :success => MAGENTA, :notify => BLUE, :info => GREEN, :debug => WHITE, :trace => BRIGHT_YELLOW, :perf => BRIGHT_MAGENTA, :host => YELLOW } @log_colors.merge!(options[:log_colors]) if options[:log_colors] # if a user overrides any of the log_colors, we will no longer # override the colors at all on a CI build. This is b/c it is # assumed that if a user is overriding the colors, they know # what they are doing. We could potentially add an additional # option a user can pass to be explicit about still allowing # the override. unless options[:log_colors] # Jenkins exposed variable - should be present on the slave directing # the beaker run ci_build = ENV['BUILD_NUMBER'] != nil @log_colors[:notify] = NORMAL if ci_build @log_colors[:info] = NORMAL if ci_build end dests = args dests << STDOUT unless options[:quiet] dests.uniq! dests.each {|dest| add_destination(dest)} end # Turn on/off STDOUT logging # @param [Boolean] off If true, disable STDOUT logging, if false enable STDOUT logging def quiet(off = true) if off remove_destination(STDOUT) #turn off the noise! else remove_destination(STDOUT) #in case we are calling this in error and we are already noisy add_destination(STDOUT) end end # Construct an array of open steams for printing log messages to # @param [Array] dest Array of strings (each used as a file path) and IO steams that messages will be printed to def add_destination(dest) case dest when IO @destinations << dest when StringIO @destinations << dest when String @destinations << File.open(dest, 'w') else raise "Unsuitable log destination #{dest.inspect}" end end # Remove a steam from the destinations array based upon it's name or file path # @param [String, IO] dest String representing a file path or IO stream def remove_destination(dest) case dest when IO @destinations.delete(dest) when StringIO @destinations.delete(dest) when String @destinations.delete_if {|d| d.respond_to?(:path) and d.path == dest} else raise "Unsuitable log destination #{dest.inspect}" end end # Are we at {LOG_LEVELS} trace? # @return [Boolean] true if 'trace' or higher, false if not 'trace' {LOG_LEVELS} or lower def is_trace? LOG_LEVELS[@log_level] >= LOG_LEVELS[:trace] end # Are we at {LOG_LEVELS} debug? # @return [Boolean] true if 'debug' or higher, false if not 'debug' {LOG_LEVELS} or lower def is_debug? LOG_LEVELS[@log_level] >= LOG_LEVELS[:debug] end # Are we at {LOG_LEVELS} verbose? # @return [Boolean] true if 'verbose' or higher, false if not 'verbose' {LOG_LEVELS} or lower def is_verbose? LOG_LEVELS[@log_level] >= LOG_LEVELS[:verbose] end # Are we at {LOG_LEVELS} warn? # @return [Boolean] true if 'warn' or higher, false if not 'warn' {LOG_LEVELS} or lower def is_warn? LOG_LEVELS[@log_level] >= LOG_LEVELS[:warn] end # Are we at {LOG_LEVELS} info? # @return [Boolean] true if 'info' or higher, false if not 'info' {LOG_LEVELS} or lower def is_info? LOG_LEVELS[@log_level] >= LOG_LEVELS[:info] end # Are we at {LOG_LEVELS} notify? # @return [Boolean] true if 'notify' or higher, false if not 'notify' {LOG_LEVELS} or lower def is_notify? LOG_LEVELS[@log_level] >= LOG_LEVELS[:notify] end # Remove invalid UTF-8 codes from provided string(s) # @param [String, Array] string The string(s) to remove invalid codes from def convert string if string.kind_of?(Array) string.map do |s| convert s end else # Remove invalid and undefined UTF-8 character encodings string = string.to_s.dup.force_encoding('UTF-8') return string.to_s.chars.select{|i| i.valid_encoding?}.join end end # Prefixes a log line with the appropriate amount of whitespace for the level # of test that's running. # # @param [String] line the line to prefix # # @return [String] the prefixed line def prefix_log_line line if line.kind_of?(Array) line.map do |s| prefix_log_line s end else line.gsub!(/\r/, '') has_ending_newline = line.end_with?("\n") actual_lines = line.split("\n") actual_lines.map! do |actual_line| @line_prefix + actual_line end new_line = actual_lines.join("\n") new_line << "\n" if has_ending_newline new_line end end # Indent the step level for the duration of block. def with_indent(&block) old_line_prefix = self.line_prefix.dup self.line_prefix << ' ' yield ensure self.line_prefix = old_line_prefix end # Sets the step level appropriately for logging to be indented correctly # # @return nil # @deprecated use {Logger#with_indent} def step_in self.line_prefix = self.line_prefix + ' ' end # Sets the step level appropriately for logging to be indented correctly # # @return nil # @deprecated use {Logger#with_indent} def step_out self.line_prefix = self.line_prefix.chop.chop end # Custom reporting for messages generated by host SUTs. # Will not print unless we are at {LOG_LEVELS} 'verbose' or higher. # Strips any color codes already in the provided messages, then adds logger color codes before reporting # @param args[Array] Strings to be reported def host_output *args return unless is_verbose? strings = strip_colors_from args string = strings.join optionally_color @log_colors[:host], string, false end # Custom reporting for messages generated by host SUTs - to preserve output # Will not print unless we are at {LOG_LEVELS} 'verbose' or higher. # Preserves outout by not stripping out colour codes # @param args[Array] Strings to be reported def color_host_output *args return unless is_verbose? string = args.join optionally_color NONE, string, false end # Custom reporting for performance/sysstat messages # Will not print unless we are at {LOG_LEVELS} 'debug' or higher. # @param args[Array] Strings to be reported def perf_output *args return unless is_debug? optionally_color @log_colors[:perf], *args end # Report a trace message. # Will not print unless we are at {LOG_LEVELS} 'trace' or higher. # @param args[Array] Strings to be reported def trace *args return unless is_trace? optionally_color @log_colors[:trace], *args end # Report a debug message. # Will not print unless we are at {LOG_LEVELS} 'debug' or higher. # @param args[Array] Strings to be reported def debug *args return unless is_verbose? optionally_color @log_colors[:debug], *args end # Report a warning message. # Will not print unless we are at {LOG_LEVELS} 'warn' or higher. # Will pre-pend the message with "Warning: ". # @param args[Array] Strings to be reported def warn *args return unless is_warn? strings = args.map {|msg| "Warning: #{msg}" } optionally_color @log_colors[:warn], strings end # Report an info message. # Will not print unless we are at {LOG_LEVELS} 'info' or higher. # @param args[Array] Strings to be reported def info *args return unless is_info? optionally_color @log_colors[:info], *args end # Report a success message. # Will always be reported. # @param args[Array] Strings to be reported def success *args optionally_color @log_colors[:success], *args end # Report a notify message. # Will not print unless we are at {LOG_LEVELS} 'notify' or higher. # @param args[Array] Strings to be reported def notify *args return unless is_notify? optionally_color @log_colors[:notify], *args end # Report an error message. # Will always be reported. # @param args[Array] Strings to be reported def error *args optionally_color @log_colors[:error], *args end # Strip any color codes from provided string(s) # @param [String] lines A single or array of lines to removed color codes from # @return [Array] An array of strings that do not have color codes def strip_colors_from lines Array( lines ).map do |line| Logger.strip_color_codes(convert(line)) end end # Print the provided message to the set destination streams, using color codes if appropriate # @param [String] color_code The color code to pre-pend to the message # @param [String] msg The message to be reported # @param [Boolean] add_newline (true) Add newlines between the color codes and the message def optionally_color color_code, msg, add_newline = true print_statement = add_newline ? :puts : :print msg = convert(msg) msg = prefix_log_line(msg) @destinations.each do |to| to.print color_code if @color to.send print_statement, msg to.print NORMAL if @color unless color_code == NONE to.flush end end # Utility method to get the current call stack and format it # to a human-readable string (which some IDEs/editors # will recognize as links to the line numbers in the trace). # Beaker associated files will be purged from backtrace unless log level is 'debug' or higher # @param [String] backtrace (caller(1)) The backtrace to format # @return [String] The formatted backtrace def pretty_backtrace backtrace = caller(1) trace = is_debug? ? backtrace : purge_harness_files_from( backtrace ) expand_symlinks( trace ).join "\n" end # Create a new StringIO log to track the current output def start_sublog if @sublog remove_destination(@sublog) end @sublog = StringIO.new add_destination(@sublog) end # Return the contents of the sublog def get_sublog @sublog.rewind @sublog.read end # Utility method to centralize dated log folder generation # # @param [String] base_dir Path of the directory for the dated log folder to live in # @param [String] log_prefix Prefix to use for the log files # @param [Time] timestamp Timestamp that should be used to generate the dated log folder # # @example base_dir = 'junit', log_prefix = 'pants', timestamp = '2015-03-04 10:35:37 -0800' # returns 'junit/pants/2015-03-04_10_35_37' # # @note since this uses 'mkdir -p', log_prefix can be a number of nested directories # # @return [String] the path of the dated log folder generated def Logger.generate_dated_log_folder(base_dir, log_prefix, timestamp) log_dir = File.join(base_dir, log_prefix, timestamp.strftime("%F_%H_%M_%S")) FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir) log_dir end #Remove color codes from provided string. Color codes are of the format /(\e\[\d\d;\d\dm)+/. #@param [String] text The string to remove color codes from #@return [String] The text without color codes def Logger.strip_color_codes(text) text.gsub(/(\e|\^\[)\[(\d*;)*\d*m/, '') end private # Expand each symlink found to its full path # Lines are assumed to be in the format "String : Integer" # @param [String] backtrace The string to search and expand symlinks in # @return [String] The backtrace with symlinks expanded def expand_symlinks backtrace backtrace.collect do |line| file_path, line_num = line.split( ":" ) expanded_path = expand_symlink File.expand_path( file_path ) expanded_path.to_s + ":" + line_num.to_s end end # Remove Beaker associated lines from a given String # @param [String] backtrace The string to remove Beaker associated lines from # @return [String] The cleaned backtrace def purge_harness_files_from backtrace mostly_purged = backtrace.reject do |line| # LOADED_FEATURES is an array of anything `require`d, i.e. everything # but the test in question $LOADED_FEATURES.any? do |require_path| line.include? require_path end end # And remove lines that contain our program name in them completely_purged = mostly_purged.reject {|line| line.include? $0 } end # Utility method that takes a path as input, checks each component # of the path to see if it is a symlink, and expands # it if it is. # @param [String] file_path The path to be examined # @return [String] The fully expanded file_path def expand_symlink file_path file_path.split( "/" ).inject do |full_path, next_dir| next_path = full_path + "/" + next_dir if File.symlink? next_path link = File.readlink next_path next_path = case link when /^\// then link else File.expand_path( full_path + "/" + link ) end end next_path end end end end beaker-4.30.0/lib/beaker/logger_junit.rb000066400000000000000000000134131407603575700200630ustar00rootroot00000000000000require 'rexml/document' module Beaker # The Beaker JUnit Logger class # This module handles message reporting from Beaker to the JUnit format # # There is a specific pattern for using this class. # Here's a list of example usages: # - {Beaker::TestSuiteResult#write_junit_xml} module LoggerJunit # writes the xml created in the block to the xml file given # # Note: Error Recovery should take place in the caller of this # method in order to recover gracefully # # @param [String] xml_file Path to the xml file # @param [String] stylesheet Path to the stylesheet file # @param [Proc] block XML message construction block # # @return nil def self.write_xml(xml_file, stylesheet, &block) doc, suites = self.get_xml_contents(xml_file, name, stylesheet) if block_given? case block.arity when 2 yield doc, suites else raise ArgumentError.new "write_xml block takes 2 arguments, not #{block.arity}" end end self.finish(doc, xml_file) end # writes out xml content for a doc # # @param [REXML::Document] doc doc containing content to write # @param [String] xml_file Path to the xml file to write # # @return nil def self.finish(doc, xml_file) # junit/name.xml will be created in a directory relative to the CWD File.open(xml_file, 'w') { |f| doc.write(f, 2) } end # gets the xml doc & suites in order to build your xml output on top of # # @param [String] xml_file Path to the xml file # @param [String] name Name of the testsuite you're writing # @param [String] stylesheet Path to the stylesheet file # # @return [REXML::Document] doc to use for your xml content # @return [REXML::Element] suites to add your content to def self.get_xml_contents(xml_file, name, stylesheet) self.copy_stylesheet_into_xml_dir(stylesheet, xml_file) xml_file_already_exists = File.file?(xml_file) doc = self.get_doc_for_filename(xml_file, stylesheet, xml_file_already_exists) suites = self.get_testsuites_from_doc(doc, name, xml_file_already_exists) return doc, suites end # copies given stylesheet into the directory of the xml file given # # @param [String] stylesheet Path to the stylesheet file # @param [String] xml_file Path to the xml file # # @return nil def self.copy_stylesheet_into_xml_dir(stylesheet, xml_file) if not File.file?(File.join(File.dirname(xml_file), File.basename(stylesheet))) FileUtils.copy(stylesheet, File.join(File.dirname(xml_file), File.basename(stylesheet))) end end # sets up doc & gives us the suites for the testsuite named # # @param [REXML::Document] doc Doc that you're getting suites from # @param [String] name Testsuite node name # @param [Boolean] already_existed Whether or not the doc already existed # # @return [Rexml::Element] testsuites def self.get_testsuites_from_doc(doc, name, already_existed) #check to see if an output file already exists, if it does add or replace test suite data if already_existed suites = REXML::XPath.first(doc, "testsuites") #remove old data suites.elements.each("testsuite") do |e| if e.name =~ /#{name}/ suites.delete_element e end end else suites = doc.add_element(REXML::Element.new('testsuites')) end return suites end # gives the document object for a particular file # # @param [String] filename Path to the file that you're opening # @param [String] stylesheet Path to the stylesheet for this doc # @param [Boolean] already_exists Whether or not the file already exists # # @return [REXML::Document] Doc that you want to write in def self.get_doc_for_filename(filename, stylesheet, already_exists) if already_exists doc = REXML::Document.new File.open(filename) else #no existing file, create a new one doc = REXML::Document.new doc << REXML::XMLDecl.new(version="1.0", encoding="UTF-8") instruction_content = "type='text/xsl' href='#{File.basename(stylesheet)}'" doc << REXML::Instruction.new(target="xml-stylesheet", content=instruction_content) end return doc end # Remove color codes and invalid XML characters from provided string # @param [String] string The string to format # @return [String] the correctly formatted cdata def self.format_cdata string self.escape_invalid_xml_chars(Logger.strip_color_codes(string)) end # Escape invalid XML UTF-8 codes from provided string, see http://www.w3.org/TR/xml/#charsets for valid # character specification # @param [String] string The string to remove invalid codes from # @return [String] Properly escaped string def self.escape_invalid_xml_chars string escaped_string = "" string.chars.each do |i| char_as_codestring = i.unpack("U*").join if self.is_valid_xml(char_as_codestring.to_i) escaped_string << i else escaped_string << "\\#{char_as_codestring}" end end escaped_string end # Determine if the provided number falls in the range of accepted xml unicode values # See http://www.w3.org/TR/xml/#charsets for valid for valid character specifications. # @param [Integer] int The number to check against # @return [Boolean] True, if the number corresponds to a valid xml unicode character, otherwise false def self.is_valid_xml(int) return ( int == 0x9 or int == 0xA or ( int >= 0x0020 and int <= 0xD7FF ) or ( int >= 0xE000 and int <= 0xFFFD ) or ( int >= 0x100000 and int <= 0x10FFFF ) ) end end end beaker-4.30.0/lib/beaker/network_manager.rb000066400000000000000000000127221407603575700205600ustar00rootroot00000000000000[ 'hypervisor' ].each do |lib| require "beaker/#{lib}" end module Beaker #Object that holds all the provisioned and non-provisioned virtual machines. #Controls provisioning, configuration, validation and cleanup of those virtual machines. class NetworkManager #Determine if a given host should be provisioned. #Provision if: # - only if we are running with ---provision # - only if we have a hypervisor # - only if either the specific hosts has no specification or has 'provision' in its config # - always if it is a vagrant box (vagrant boxes are always provisioned as # they always need ssh key hacking.) def provision? options, host command_line_says = options[:provision] host_says = host['hypervisor'] && (host.has_key?('provision') ? host['provision'] : true) (command_line_says && host_says) or (host['hypervisor'] =~/(vagrant)/) end attr_accessor :hosts, :hypervisors def initialize(options, logger) @logger = logger @options = options @hosts = [] @machines = {} @hypervisors = nil # user provided prefix has top priority if not @options[:log_prefix] # name it after the hosts file if @options[:hosts_file] @options[:log_prefix] = File.basename(@options[:hosts_file], '.yml') else #here be the default @options[:log_prefix] = @options[:default_log_prefix] end end @options[:timestamp] = Time.now unless @options.has_key?(:timestamp) @options[:xml_dated_dir] = Beaker::Logger.generate_dated_log_folder(@options[:xml_dir], @options[:log_prefix], @options[:timestamp]) @options[:log_dated_dir] = Beaker::Logger.generate_dated_log_folder(@options[:log_dir], @options[:log_prefix], @options[:timestamp]) @options[:logger_sut] = Beaker::Logger.new(File.join(@options[:log_dated_dir], @options[:log_sut_event]), { :quiet => true }) end #Provision all virtual machines. Provision machines according to their set hypervisor, if no hypervisor #is selected assume that the described hosts are already up and reachable and do no provisioning. def provision if @hypervisors cleanup end @hypervisors = {} #sort hosts by their hypervisor, use hypervisor 'none' if no hypervisor is specified hostless_options = Beaker::Options::OptionsHash.new.merge(@options.select{ |k,v| k.to_s !~ /HOSTS/}) @options['HOSTS'].each_key do |name| host_hash = @options['HOSTS'][name] hypervisor = host_hash['hypervisor'] if @options[:provision] hypervisor = provision?(@options, host_hash) ? host_hash['hypervisor'] : 'none' end @logger.debug "Hypervisor for #{name} is #{hypervisor}" @machines[hypervisor] = [] unless @machines[hypervisor] hostless_options[:timesync] = host_hash[:timesync] if host_hash[:timesync]!=nil host_itself = Beaker::Host.create(name, host_hash, hostless_options) host_itself.validate_setup @machines[hypervisor] << host_itself end @machines.each_key do |type| @hypervisors[type] = Beaker::Hypervisor.create(type, @machines[type], @options) @hosts << @machines[type] @machines[type].each do |host| log_sut_event host, true end end @hosts = @hosts.flatten @hosts end #Validate all provisioned machines, ensure that required packages are installed - if they are missing #attempt to add them. #@raise [Exception] Raise an exception if virtual machines fail to be validated def validate if @hypervisors @hypervisors.each_key do |type| @hypervisors[type].validate end end end #Configure all provisioned machines, adding any packages or settings required for SUTs #@raise [Exception] Raise an exception if virtual machines fail to be configured def configure if @hypervisors @hypervisors.each_key do |type| @hypervisors[type].configure end end end # configure proxy on all provioned machines #@raise [Exception] Raise an exception if virtual machines fail to be configured def proxy_package_manager if @hypervisors @hypervisors.each_key do |type| @hypervisors[type].proxy_package_manager end end end #Shut down network connections and revert all provisioned virtual machines def cleanup #shut down connections @hosts.each {|host| host.close } if @hypervisors @hypervisors.each_key do |type| @hypervisors[type].cleanup @hypervisors[type].instance_variable_get(:@hosts).each do |host| log_sut_event host, false end end end @hypervisors = nil end # logs provisioning events # # @param [Host] host The host that the event is happening to # @param [Boolean] create Whether the event is creation or cleaning up # # @return [String] the log line created for this event def log_sut_event host, create raise ArgumentError.new "log_sut_event called before sut logger created. skipping #{host}, #{create}" unless @options.has_key?(:logger_sut) sut_logger = @options[:logger_sut] time = Time.new stamp = time.strftime('%Y-%m-%d %H:%M:%S') verb = create ? '+' : '-' line = "#{stamp}\t[#{verb}]\t#{host['hypervisor']}\t#{host['platform']}\t#{host.log_prefix}" sut_logger.notify line line end end end beaker-4.30.0/lib/beaker/options.rb000066400000000000000000000002671407603575700170710ustar00rootroot00000000000000%w(validator options_hash presets command_line_parser options_file_parser hosts_file_parser parser subcommand_options_file_parser).each do |lib| require "beaker/options/#{lib}" end beaker-4.30.0/lib/beaker/options/000077500000000000000000000000001407603575700165375ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/options/command_line_parser.rb000066400000000000000000000311031407603575700230630ustar00rootroot00000000000000module Beaker module Options #An object that parses arguments in the format ['--option', 'value', '--option2', 'value2', '--switch'] class CommandLineParser # @example Create a CommanLineParser # a = CommandLineParser.new # # @note All of Beaker's supported command line options are defined here def initialize @cmd_options = Beaker::Options::OptionsHash.new @optparse = OptionParser.new do|opts| # Set a banner opts.banner = "Usage: #{File.basename($0)} [options...]" opts.on '-h', '--hosts FILE', 'Use host configuration FILE', 'Possible FILE values:', 'a file path (beaker will parse file directly)', 'a beaker-hostgenerator string (BHG generates hosts file)', 'omitted (coordinator-only run; no SUTs provisioned)' do |file| @cmd_options[:hosts_file] = file end opts.on '-o', '--options-file FILE', 'Read options from FILE', 'This should evaluate to a ruby hash.', 'CLI optons are given precedence.' do |file| @cmd_options[:options_file] = file end opts.on '--helper PATH/TO/SCRIPT', 'Ruby file evaluated prior to tests', '(a la spec_helper)' do |script| @cmd_options[:helper] = script end opts.on '--load-path /PATH/TO/DIR,/ADDITIONAL/DIR/PATHS', 'Add paths to LOAD_PATH' do |value| @cmd_options[:load_path] = value end opts.on '-t', '--tests /PATH/TO/DIR,/ADDITIONA/DIR/PATHS,/PATH/TO/FILE.rb', 'Execute tests from paths and files' do |value| @cmd_options[:tests] = value end opts.on '--pre-suite /PRE-SUITE/DIR/PATH,/ADDITIONAL/DIR/PATHS,/PATH/TO/FILE.rb', 'Path to project specific steps to be run BEFORE testing' do |value| @cmd_options[:pre_suite] = value end opts.on '--post-suite /POST-SUITE/DIR/PATH,/OPTIONAL/ADDITONAL/DIR/PATHS,/PATH/TO/FILE.rb', 'Path to project specific steps to be run AFTER testing' do |value| @cmd_options[:post_suite] = value end opts.on '--pre-cleanup /PRE-CLEANUP/DIR/PATH,/OPTIONAL/ADDITONAL/DIR/PATHS,/PATH/TO/FILE.rb', 'Path to project specific steps to be run before cleaning up VMs (will always run)' do |value| @cmd_options[:pre_cleanup] = value end opts.on '--preserve-state', 'Preserve the state of the host vm hash for a beaker exec run', 'This adds any additional host settings that are defined', 'during a beaker subcommand run to .beaker/subcommand_options.yaml,', 'allowing us to preserve state across beaker subcommand runs.' do |bool| @cmd_options[:preserve_state] = bool end opts.on '--[no-]provision', 'Do not provision vm images before testing', '(default: true)' do |bool| @cmd_options[:provision] = bool unless bool @cmd_options[:validate] = false @cmd_options[:configure] = false end end opts.on '--[no-]configure', 'Do not configure vm images before testing', '(default: true)' do |bool| @cmd_options[:configure] = bool end opts.on '--preserve-hosts [MODE]', 'How should SUTs be treated post test', 'Possible values:', 'always (keep SUTs alive)', 'onfail (keep SUTs alive if failures occur during testing)', 'onpass (keep SUTs alive if no failures occur during testing)', 'never (cleanup SUTs - shutdown and destroy any changes made during testing)', '(default: never)' do |mode| @cmd_options[:preserve_hosts] = mode || 'always' end opts.on '--debug-errors', 'Enter a pry console if or when a test fails', '(default: false)' do |bool| @cmd_options[:debug_errors] = bool end opts.on '--exec-manual-tests', 'Execute manual tests', '(default: false)' do |bool| @cmd_options[:exec_manual_tests] = bool end opts.on '--root-keys', 'Install puppetlabs pubkeys for superuser', '(default: false)' do |bool| @cmd_options[:root_keys] = bool end opts.on '--keyfile /PATH/TO/SSH/KEY', 'Specify alternate SSH key', '(default: ~/.ssh/id_rsa)' do |key| @cmd_options[:keyfile] = key end opts.on '--timeout TIMEOUT', '(vCloud only) Specify a provisioning timeout (in seconds)', '(default: 300)' do |value| @cmd_options[:timeout] = value end opts.on '-i URI', '--install URI', 'Install a project repo/app on the SUTs', 'Provide full git URI or use short form KEYWORD/name', 'supported keywords: PUPPET, FACTER, HIERA, HIERA-PUPPET' do |value| @cmd_options[:install] = value end opts.on('-m', '--modules URI', 'Select puppet module git install URI') do |value| @cmd_options[:modules] = value end opts.on '-q', '--[no-]quiet', 'Do not log output to STDOUT', '(default: false)' do |bool| @cmd_options[:quiet] = bool end opts.on '--[no-]color', 'Do not display color in log output', '(default: true)' do |bool| @cmd_options[:color] = bool end opts.on '--[no-]color-host-output', 'Ensure SUT colored output is preserved', '(default: false)' do |bool| @cmd_options[:color_host_output] = bool if bool @cmd_options[:color_host_output] = true end end opts.on '--log-level LEVEL', 'Log level', 'Supported LEVEL keywords:', 'trace : all messages, full stack trace of errors, file copy details', 'debug : all messages, plus full stack trace of errors', 'verbose : all messages', 'info : info messages, notifications and warnings', 'notify : notifications and warnings', 'warn : warnings only', '(default: info)' do |val| @cmd_options[:log_level] = val end opts.on '--log-prefix PREFIX', 'Use a custom prefix for your Beaker log files', 'can provide nested directories (ie. face/man)', '(defaults to hostfile name. ie. ../i/07.yml --> "07")' do |val| @cmd_options[:log_prefix] = val end opts.on '-d', '--[no-]dry-run', 'Report what would happen on targets', '(default: false)' do |bool| @cmd_options[:dry_run] = bool end opts.on '--fail-mode [MODE]', 'How should the harness react to errors/failures', 'Possible values:', 'fast (skip all subsequent tests)', 'slow (attempt to continue run post test failure)', 'stop (DEPRECATED, please use fast)', '(default: slow)' do |mode| @cmd_options[:fail_mode] = mode =~ /stop/ ? 'fast' : mode end opts.on '--test-results-file /FILE/TO/SAVE/TO.rb', 'Path to persist errors/failures from the current run to', '(default: not saved)' do |value| @cmd_options[:test_results_file] = value end opts.on '--[no-]ntp', 'Sync time on SUTs before testing', '(default: false)' do |bool| @cmd_options[:timesync] = bool end opts.on '--repo-proxy', 'Proxy packaging repositories on ubuntu, debian, cumulus and solaris-11', '(default: false)' do @cmd_options[:repo_proxy] = true end opts.on '--add-el-extras', 'Add Extra Packages for Enterprise Linux (EPEL) repository to el-* hosts', '(default: false)' do @cmd_options[:add_el_extras] = true end opts.on '--package-proxy URL', 'Set proxy url for package managers (yum and apt)' do |value| @cmd_options[:package_proxy] = value end opts.on '--[no-]validate', 'Validate that SUTs are correctly provisioned before running tests', '(default: true)' do |bool| @cmd_options[:validate] = bool end opts.on '--collect-perf-data [MODE]', 'Collect SUT performance and load data', 'Possible values:', 'aggressive (poll every minute)', 'normal (poll every 10 minutes)', 'none (do not collect perf data)', '(default: normal)' do |mode| @cmd_options[:collect_perf_data] = mode || 'normal' end opts.on('--version', 'Report currently running version of beaker' ) do @cmd_options[:beaker_version_print] = true end opts.on('--parse-only', 'Display beaker parsed options and exit' ) do @cmd_options[:parse_only] = true end opts.on('--help', 'Display this screen' ) do @cmd_options[:help] = true end opts.on '-c', '--config FILE', 'DEPRECATED, use --hosts' do |file| @cmd_options[:hosts_file] = file end opts.on '--[no-]debug', 'DEPRECATED, use --log-level' do |bool| @cmd_options[:log_level] = bool ? 'debug' : 'info' end opts.on '-x', '--[no-]xml', 'DEPRECATED - JUnit XML now generated by default' do #noop end opts.on '--type TYPE', 'DEPRECATED - pe/foss/aio determined during runtime' do |type| #backwards compatability, oh how i hate you @cmd_options[:type] = type end opts.on '--tag TAGS', 'DEPRECATED - use --test-tag-and instead' do |value| @cmd_options[:test_tag_and] = value end opts.on '--test-tag-and TAGS', 'Run the set of tests matching ALL of the provided single or comma separated list of tags' do |value| @cmd_options[:test_tag_and] = value end opts.on '--test-tag-or TAGS', 'Run the set of tests matching ANY of the provided single or comma separated list of tags' do |value| @cmd_options[:test_tag_or] = value end opts.on '--exclude-tag TAGS', 'DEPRECATED - use --test-tag-exclude instead' do |value| @cmd_options[:test_tag_exclude] = value end opts.on '--test-tag-exclude TAGS', 'Run the set of tests that do not contain ANY of the provided single or comma separated list of tags' do |value| @cmd_options[:test_tag_exclude] = value end opts.on '--xml-time-order', 'Output an additional JUnit XML file, sorted by execution time' do |bool| @cmd_options[:xml_time_enabled] = bool end end end # Parse an array of arguments into a Hash of options # @param [Array] args The array of arguments to consume # # @example # args = ['--option', 'value', '--option2', 'value2', '--switch'] # parser = CommandLineParser.new # parser.parse(args) == {:option => 'value, :options2 => value, :switch => true} # # @return [Hash] Return the Hash of options def parse( args = ARGV ) @optparse.parse(args) @cmd_options end # Generate a string representing the supported arguments # # @example # parser = CommandLineParser.new # parser.usage = "Options: ..." # # @return [String] Return a string representing the available arguments def usage @optparse.help end end end end beaker-4.30.0/lib/beaker/options/hosts_file_parser.rb000066400000000000000000000066621407603575700226110ustar00rootroot00000000000000module Beaker module Options #A set of functions to parse hosts files module HostsFileParser # Read the contents of the hosts.cfg into an OptionsHash, merge the 'CONFIG' section into the OptionsHash, return OptionsHash # @param [String] hosts_file_path The path to the hosts file # # @example # hosts_hash = HostsFileParser.parse_hosts_file('sample.cfg') # hosts_hash == {:HOSTS=>{:"pe-ubuntu-lucid"=>{:roles=>["agent", "dashboard", "database", "master"], ... } # # @return [OptionsHash] The contents of the hosts file as an OptionsHash # @raise [ArgumentError] Raises if hosts_file_path is not a valid YAML file # @raise [Errno::ENOENT] File not found error: hosts_file doesn't exist def self.parse_hosts_file(hosts_file_path = nil) require 'erb' host_options = new_host_options return host_options unless hosts_file_path error_message = "#{hosts_file_path} is not a valid YAML file\n\t" host_options = self.merge_hosts_yaml( host_options, error_message ) { hosts_file_path = File.expand_path( hosts_file_path ) raise "#{hosts_file_path} is not a valid path" unless File.exist?(hosts_file_path) YAML.load(ERB.new(File.read(hosts_file_path), nil, '-').result(binding)) } fix_roles_array( host_options ) end # Read the contents of a host definition as a string into an OptionsHash # # @param [String] hosts_def_yaml YAML hosts definition # # @return [OptionsHash] Contents of the hosts file as an OptionsHash # @raise [ArgumentError] If hosts_def_yaml is not a valid YAML string def self.parse_hosts_string(hosts_def_yaml = nil) require 'erb' host_options = new_host_options return host_options unless hosts_def_yaml error_message = "#{hosts_def_yaml}\nis not a valid YAML string\n\t" host_options = self.merge_hosts_yaml( host_options, error_message ) { YAML.load(ERB.new(hosts_def_yaml, nil, '-').result(binding)) } fix_roles_array( host_options ) end # Convenience method to create new OptionsHashes with a HOSTS section # # @return [OptionsHash] Hash with HOSTS section def self.new_host_options host_options = Beaker::Options::OptionsHash.new host_options['HOSTS'] ||= {} host_options end # Make sure the roles array is present for all hosts # def self.fix_roles_array( host_options ) host_options['HOSTS'].each_key do |host| host_options['HOSTS'][host]['roles'] ||= [] end if host_options.has_key?('CONFIG') host_options = host_options.merge(host_options.delete('CONFIG')) end host_options end # Merges YAML read in the passed block into given OptionsHash # # @param [OptionsHash] host_options Host information hash # @param [String] error_message Message to print if {::Psych::SyntaxError} # is raised during block execution # @return [OptionsHash] Updated host_options with host info merged def self.merge_hosts_yaml( host_options, error_message, &block ) begin loaded_host_options = yield rescue Psych::SyntaxError => e error_message << e.to_s raise ArgumentError, error_message end host_options.merge( loaded_host_options ) end end end end beaker-4.30.0/lib/beaker/options/options_file_parser.rb000066400000000000000000000035021407603575700231320ustar00rootroot00000000000000require 'open-uri' module Beaker module Options #A set of functions to read options files module OptionsFileParser # Eval the contents of options_file_path, return as an OptionsHash # # Options file is assumed to contain extra options stored in a Hash # # ie, # { # :debug => true, # :tests => "test.rb", # } # # @param [String] options_file_path The path to the options file # # @example # options_hash = OptionsFileParser.parse_options_file('sample.cfg') # options_hash == {:debug=>true, :tests=>"test.rb", :pre_suite=>["pre-suite.rb"], :post_suite=>"post_suite1.rb,post_suite2.rb"} # # @return [OptionsHash] The contents of the options file as an OptionsHash # @raise [ArgumentError] Raises if options_file_path is not a path to a file # @note Since the options_file is Eval'ed, any other Ruby commands will also be executed, this can be used # to set additional environment variables def self.parse_options_file(options_file_path) result = Beaker::Options::OptionsHash.new if options_file_path options_file_path = File.expand_path(options_file_path) unless File.exists?(options_file_path) raise ArgumentError, "Specified options file '#{options_file_path}' does not exist!" end # This eval will allow the specified options file to have access to our # scope. It is important that the variable 'options_file_path' is # accessible, because some existing options files (e.g. puppetdb) rely on # that variable to determine their own location (for use in 'require's, etc.) result = result.merge(eval(File.read(options_file_path))) end result end end end end beaker-4.30.0/lib/beaker/options/options_hash.rb000066400000000000000000000022721407603575700215650ustar00rootroot00000000000000require 'stringify-hash' module Beaker module Options # A hash that treats Symbol and String keys interchangeably # and recursively merges hashes class OptionsHash < StringifyHash # Determine if type of ObjectHash is pe, defaults to true # # @example Use this method to test if the :type setting is pe # a['type'] = 'pe' # a.is_pe? == true # # @return [Boolean] def is_pe? self[:type] ? self[:type] =~ /pe/ : true end # Determine the puppet type of the ObjectHash # # Default is FOSS # # @example Use this method to test if the :type setting is pe # a['type'] = 'pe' # a.get_type == :pe # # @return [Symbol] the type given in the options def get_type case self[:type] when /pe/ :pe when /foss/ :foss else :foss end end def dump_to_file(output_file) dirname = File.dirname(output_file) unless File.directory?(dirname) FileUtils.mkdir_p(dirname) end File.open(output_file, 'w+') { |f| f.write(dump) } end end end end beaker-4.30.0/lib/beaker/options/parser.rb000066400000000000000000000444641407603575700203740ustar00rootroot00000000000000require 'yaml' module Beaker module Options #An Object that parses, merges and normalizes all supported Beaker options and arguments class Parser GITREPO = 'git://github.com/puppetlabs' #These options can have the form of arg1,arg2 or [arg] or just arg, #should default to [] LONG_OPTS = [:helper, :load_path, :tests, :pre_suite, :post_suite, :install, :pre_cleanup, :modules] #These options expand out into an array of .rb files RB_FILE_OPTS = [:tests, :pre_suite, :post_suite, :pre_cleanup] PARSE_ERROR = Psych::SyntaxError #The OptionsHash of all parsed options attr_accessor :options attr_reader :attribution # Returns the git repository used for git installations # @return [String] The git repository def repo GITREPO end # Returns a description of Beaker's supported arguments # @return [String] The usage String def usage @command_line_parser.usage end # Normalizes argument into an Array. Argument can either be converted into an array of a single value, # or can become an array of multiple values by splitting arg over ','. If argument is already an # array that array is returned untouched. # @example # split_arg([1, 2, 3]) == [1, 2, 3] # split_arg(1) == [1] # split_arg("1,2") == ["1", "2"] # split_arg(nil) == [] # @param [Array, String] arg Either an array or a string to be split into an array # @return [Array] An array of the form arg, [arg], or arg.split(',') def split_arg arg arry = [] if arg.is_a?(Array) arry += arg elsif arg =~ /,/ arry += arg.split(',') else arry << arg end arry end # Generates a list of files based upon a given path or list of paths. # # Looks recursively for .rb files in paths. # # @param [Array] paths Array of file paths to search for .rb files # @return [Array] An Array of fully qualified paths to .rb files # @raise [ArgumentError] Raises if no .rb files are found in searched directory or if # no .rb files are found overall def file_list(paths) files = [] if !paths.empty? paths.each do |root| @validator.validate_path(root) path_files = [] if File.file?(root) path_files << root elsif File.directory?(root) #expand and explore path_files = Dir.glob(File.join(root, '**/*.rb')) .select { |f| File.file?(f) } .sort_by { |file| [file.count('/'), file] } end @validator.validate_files(path_files, root) files += path_files end end @validator.validate_files(files, paths.to_s) files end # resolves all file symlinks that require it. This modifies @options. # # @note doing it here allows us to not need duplicate logic, which we # would need if we were doing it in the parser (--hosts & --config) # # @return nil # @api public def resolve_symlinks! if @options[:hosts_file] && !@options[:hosts_file_generated] @options[:hosts_file] = File.realpath(@options[:hosts_file]) end end #Converts array of paths into array of fully qualified git repo URLS with expanded keywords # #Supports the following keywords # PUPPET # FACTER # HIERA # HIERA-PUPPET #@example # opts = ["PUPPET/3.1"] # parse_git_repos(opts) == ["#{GITREPO}/puppet.git#3.1"] #@param [Array] git_opts An array of paths #@return [Array] An array of fully qualified git repo URLs with expanded keywords def parse_git_repos(git_opts) git_opts.map! { |opt| case opt when /^PUPPET\// opt = "#{repo}/puppet.git##{opt.split('/', 2)[1]}" when /^FACTER\// opt = "#{repo}/facter.git##{opt.split('/', 2)[1]}" when /^HIERA\// opt = "#{repo}/hiera.git##{opt.split('/', 2)[1]}" when /^HIERA-PUPPET\// opt = "#{repo}/hiera-puppet.git##{opt.split('/', 2)[1]}" end opt } git_opts end #Add the 'default' role to the host determined to be the default. If a host already has the role default then #do nothing. If more than a single host has the role 'default', raise error. #Default host determined to be 1) the only host in a single host configuration, 2) the host with the role 'master' #defined. #@param [Hash] hosts A hash of hosts, each identified by a String name. Each named host will have an Array of roles def set_default_host!(hosts) default = [] master = [] default_host_name = nil #look through the hosts and find any hosts with role 'default' and any hosts with role 'master' hosts.each_key do |name| host = hosts[name] if host[:roles].include?('default') default << name elsif host[:roles].include?('master') master << name end end # default_set? will throw an error if length > 1 # and return false if no default is set. if !@validator.default_set?(default) #no default set, let's make one if not master.empty? and master.length == 1 default_host_name = master[0] elsif hosts.length == 1 default_host_name = hosts.keys[0] end if default_host_name hosts[default_host_name][:roles] << 'default' end end end #Constructor for Parser # def initialize @command_line_parser = Beaker::Options::CommandLineParser.new @presets = Beaker::Options::Presets.new @validator = Beaker::Options::Validator.new @attribution = Beaker::Options::OptionsHash.new end # Update the @attribution hash with the source of each key in the options_hash # # @param [Hash] options_hash Options hash # @param [String] source Where the options were specified # @return [Hash] hash Hash of sources for each key def tag_sources(options_hash, source) hash = Beaker::Options::OptionsHash.new options_hash.each do |key, value| if value.is_a?(Hash) hash[key] = tag_sources(value, source) else hash[key] = source end end hash end # Update the @option hash with a value and the @attribution hash with a source # # @param [String] key The key to update in both hashes # @param [Object] value The value to set in the @options hash # @param [String] source The source to set in the @attribution hash def update_option(key, value, source) @options[key] = value @attribution[key] = source end # Parses ARGV or provided arguments array, file options, hosts options and combines with environment variables and # preset defaults to generate a Hash representing the Beaker options for a given test run # # Order of priority is as follows: # 1. environment variables are given top priority # 2. ARGV or provided arguments array # 3. the 'CONFIG' section of the hosts file # 4. options file values # 5. subcommand options, if executing beaker subcommands # 6. subcommand options from $HOME/.beaker/subcommand_options.yaml # 7. project values in .beaker.yml # 8. default or preset values are given the lowest priority # # @param [Array] args ARGV or a provided arguments array # @raise [ArgumentError] Raises error on bad input def parse_args(args = ARGV) @options = @presets.presets @attribution = @attribution.merge(tag_sources(@presets.presets, "preset")) cmd_line_options = @command_line_parser.parse(args) cmd_line_options[:command_line] = ([$0] + args).join(' ') @attribution = @attribution.merge(tag_sources(cmd_line_options, "flag")) # Merge options in reverse precedence order. First project options, # then global options from $HOME/.beaker/subcommand_options.yaml, # then subcommand options in the project. subcommand_options_file = Beaker::Subcommands::SubcommandUtil::SUBCOMMAND_OPTIONS { "project" => ".beaker.yml", "homedir" => "#{ENV['HOME']}/#{subcommand_options_file}", "subcommand" => subcommand_options_file, }.each_pair do |src, path| opts = if src == "project" Beaker::Options::SubcommandOptionsParser.parse_options_file(path) else Beaker::Options::SubcommandOptionsParser.parse_subcommand_options(args, path) end @attribution = @attribution.merge(tag_sources(opts, src)) @options.merge!(opts) end file_options = Beaker::Options::OptionsFileParser.parse_options_file(cmd_line_options[:options_file] || options[:options_file]) @attribution = @attribution.merge(tag_sources(file_options, "options_file")) # merge together command line and file_options # overwrite file options with command line options cmd_line_and_file_options = file_options.merge(cmd_line_options) # merge command line and file options with defaults # overwrite defaults with command line and file options @options = @options.merge(cmd_line_and_file_options) if not @options[:help] and not @options[:beaker_version_print] hosts_options = parse_hosts_options # merge in host file vars # overwrite options (default, file options, command line) with host file options @options = @options.merge(hosts_options) @attribution = @attribution.merge(tag_sources(hosts_options, "host_file")) # re-merge the command line options # overwrite options (default, file options, hosts file ) with command line arguments @options = @options.merge(cmd_line_options) @attribution = @attribution.merge(tag_sources(cmd_line_options, "cmd")) # merge in env vars # overwrite options (default, file options, command line, hosts file) with env env_vars = @presets.env_vars @options = @options.merge(env_vars) @attribution = @attribution.merge(tag_sources(env_vars, "env")) normalize_args end @options end # Parse hosts options from host files into a host options hash. Falls back # to trying as a beaker-hostgenerator string if reading the hosts file # doesn't work # # @return [Hash] Host options, containing all host-specific details # @raise [ArgumentError] if a hosts file is generated, but it can't # be read by the HostsFileParser def parse_hosts_options if @options[:hosts_file].nil? || File.exists?(@options[:hosts_file]) #read the hosts file that contains the node configuration and hypervisor info return Beaker::Options::HostsFileParser.parse_hosts_file(@options[:hosts_file]) end dne_message = "\nHosts file '#{@options[:hosts_file]}' does not exist." dne_message << "\nTrying as beaker-hostgenerator input.\n\n" $stdout.puts dne_message require 'beaker-hostgenerator' host_generator_options = [ @options[:hosts_file] ] host_generator_options += [ '--hypervisor', ENV['BEAKER_HYPERVISOR'] ] if ENV['BEAKER_HYPERVISOR'] hosts_file_content = begin bhg_cli = BeakerHostGenerator::CLI.new(host_generator_options) bhg_cli.execute rescue BeakerHostGenerator::Exceptions::Error, BeakerHostGenerator::Exceptions::InvalidNodeSpecError => error error_message = "\nbeaker-hostgenerator was not able to use this value as input." error_message << "\nExiting with an Error.\n\n" $stderr.puts error_message raise error end @options[:hosts_file_generated] = true Beaker::Options::HostsFileParser.parse_hosts_string( hosts_file_content ) end #Validate all merged options values for correctness # #Currently checks: # - each host has a valid platform # - if a keyfile is provided then use it # - paths provided to --test, --pre-suite, --post-suite provided lists of .rb files for testing # - --fail-mode is one of 'fast', 'stop' or nil # - if using blimpy hypervisor an EC2 YAML file exists # - if using the aix, solaris, or vcloud hypervisors a .fog file exists # - that one and only one master is defined per set of hosts # - that solaris/windows/aix hosts are agent only for PE tests OR # - sets the default host based upon machine definitions # - if an ssh user has been defined make it the host user # #@raise [ArgumentError] Raise if argument/options values are invalid def normalize_args @options['HOSTS'].each_key do |name| @validator.validate_platform(@options['HOSTS'][name], name) @options['HOSTS'][name]['platform'] = Platform.new(@options['HOSTS'][name]['platform']) end #use the keyfile if present if @options.has_key?(:keyfile) @options[:ssh][:keys] = [@options[:keyfile]] end #split out arguments - these arguments can have the form of arg1,arg2 or [arg] or just arg #will end up being normalized into an array LONG_OPTS.each do |opt| if @options.has_key?(opt) update_option(opt, split_arg(@options[opt]), 'runtime') if RB_FILE_OPTS.include?(opt) && (not @options[opt] == []) update_option(opt, file_list(@options[opt]), 'runtime') end if opt == :install update_option(:install, parse_git_repos(@options[:install]), 'runtime') end else update_option(opt, [], 'runtime') end end @validator.validate_fail_mode(@options[:fail_mode]) @validator.validate_preserve_hosts(@options[:preserve_hosts]) #check for config files necessary for different hypervisors hypervisors = get_hypervisors(@options[:HOSTS]) hypervisors.each do |visor| check_hypervisor_config(visor) end #check that roles of hosts make sense # - must be one and only one master master = 0 roles = get_roles(@options[:HOSTS]) roles.each do |role_array| master += 1 if role_array.include?('master') @validator.validate_frictionless_roles(role_array) end @validator.validate_master_count(master) #check that windows/el-4 boxes are only agents (solaris can be a master in foss cases) @options[:HOSTS].each_key do |name| host = @options[:HOSTS][name] if host[:platform] =~ /windows|el-4/ test_host_roles(name, host) end #check to see if a custom user account has been provided, if so use it if host[:ssh] && host[:ssh][:user] host[:user] = host[:ssh][:user] end # merge host tags for this host with the global/preset host tags host[:host_tags] = @options[:host_tags].merge(host[:host_tags] || {}) end normalize_test_tags! @validator.validate_test_tags( @options[:test_tag_and], @options[:test_tag_or], @options[:test_tag_exclude] ) resolve_symlinks! #set the default role set_default_host!(@options[:HOSTS]) end # Get an array containing lists of roles by parsing each host in hosts. # # @param [Array>] hosts beaker hosts # @return [Array] roles [['master', 'database'], ['agent'], ...] def get_roles(hosts) roles = [] hosts.each_key do |name| roles << hosts[name][:roles] end roles end # Get a unique list of hypervisors from list of host. # # @param [Array] hosts beaker hosts # @return [Array] unique list of hypervisors def get_hypervisors(hosts) hypervisors = [] hosts.each_key { |name| hypervisors << hosts[name][:hypervisor].to_s } hypervisors.uniq end # Validate the config file for visor exists. # # @param [String] visor Hypervisor name # @return [nil] no return # @raise [ArgumentError] Raises error if config file does not exist or is not valid YAML def check_hypervisor_config(visor) if ['blimpy'].include?(visor) @validator.check_yaml_file(@options[:ec2_yaml], "required by #{visor}") end if %w(aix solaris vcloud).include?(visor) @validator.check_yaml_file(@options[:dot_fog], "required by #{visor}") end end # Normalize include and exclude tags. This modifies @options. # # @note refer to {Beaker::DSL::TestTagging} for test tagging implementation # def normalize_test_tags! @options[:test_tag_and] ||= '' @options[:test_tag_or] ||= '' @options[:test_tag_exclude] ||= '' @options[:test_tag_and] = @options[:test_tag_and].split(',') if @options[:test_tag_and].respond_to?(:split) @options[:test_tag_or] = @options[:test_tag_or].split(',') if @options[:test_tag_or].respond_to?(:split) @options[:test_tag_exclude] = @options[:test_tag_exclude].split(',') if @options[:test_tag_exclude].respond_to?(:split) @options[:test_tag_and].map!(&:downcase) @options[:test_tag_or].map!(&:downcase) @options[:test_tag_exclude].map!(&:downcase) end private # @api private def test_host_roles(host_name, host_hash) exclude_roles = %w(master database dashboard) host_roles = host_hash[:roles] unless (host_roles & exclude_roles).empty? @validator.parser_error "#{host_hash[:platform].to_s} box '#{host_name}' may not have roles: #{exclude_roles.join(', ')}." end end end end end beaker-4.30.0/lib/beaker/options/presets.rb000066400000000000000000000233601407603575700205550ustar00rootroot00000000000000module Beaker module Options #A class representing the environment variables and preset argument values to be incorporated #into the Beaker options Object. class Presets # This is a constant that describes the variables we want to collect # from the environment. The keys correspond to the keys in # `presets` (flattened) The values are an optional array of # environment variable names to look for. The array structure allows # us to define multiple environment variables for the same # configuration value. They are checked in the order they are arrayed # so that preferred and "fallback" values work as expected. # # 'JOB_NAME' and 'BUILD_URL' envs are supplied by Jenkins # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project ENVIRONMENT_SPEC = { :home => 'HOME', :project => ['BEAKER_PROJECT', 'BEAKER_project', 'JOB_NAME'], :department => ['BEAKER_DEPARTMENT', 'BEAKER_department'], :jenkins_build_url => ['BEAKER_BUILD_URL', 'BUILD_URL'], :created_by => ['BEAKER_CREATED_BY'], :consoleport => ['BEAKER_CONSOLEPORT', 'consoleport'], :is_pe => ['BEAKER_IS_PE', 'IS_PE'], :pe_dir => ['BEAKER_PE_DIR', 'pe_dist_dir'], :puppet_agent_version => ['BEAKER_PUPPET_AGENT_VERSION'], :puppet_agent_sha => ['BEAKER_PUPPET_AGENT_SHA'], :puppet_collection => ['BEAKER_PUPPET_COLLECTION'], :pe_version_file => ['BEAKER_PE_VERSION_FILE', 'pe_version_file'], :pe_ver => ['BEAKER_PE_VER', 'pe_ver'], :forge_host => ['BEAKER_FORGE_HOST', 'forge_host'], :package_proxy => ['BEAKER_PACKAGE_PROXY'], :release_apt_repo_url => ['BEAKER_RELEASE_APT_REPO', 'RELEASE_APT_REPO'], :release_yum_repo_url => ['BEAKER_RELEASE_YUM_REPO', 'RELEASE_YUM_REPO'], :dev_builds_url => ['BEAKER_DEV_BUILDS_URL', 'DEV_BUILDS_URL'], :vbguest_plugin => ['BEAKER_VB_GUEST_PLUGIN', 'BEAKER_vb_guest_plugin'], :test_tag_and => ['BEAKER_TAG', 'BEAKER_TEST_TAG_AND'], :test_tag_or => ['BEAKER_TEST_TAG_OR'], :test_tag_exclude => ['BEAKER_EXCLUDE_TAG', 'BEAKER_TEST_TAG_EXCLUDE'], :run_in_parallel => ['BEAKER_RUN_IN_PARALLEL'], } # Select all environment variables whose name matches provided regex # @return [Hash] Hash of environment variables def select_env_by_regex regex envs = Beaker::Options::OptionsHash.new ENV.each_pair do | k, v | if k.to_s =~ /#{regex}/ envs[k] = v end end envs end # Takes an environment_spec and searches the processes environment variables accordingly # # @param [Hash{Symbol=>Array,String}] env_var_spec the spec of what env vars to search for # # @return [Hash] Found environment values def collect_env_vars( env_var_spec ) env_var_spec.inject({}) do |memo, key_value| key, value = key_value[0], key_value[1] set_env_var = Array(value).detect {|possible_variable| ENV[possible_variable] } memo[key] = ENV[set_env_var] if set_env_var memo end end # Takes a hash where the values are found environment configuration values # and formats them to appropriate Beaker configuration values # # @param [Hash{Symbol=>String}] found_env_vars Environment variables to munge # # @return [Hash] Environment config values formatted appropriately def format_found_env_vars( found_env_vars ) found_env_vars[:consoleport] &&= found_env_vars[:consoleport].to_i if found_env_vars[:is_pe] is_pe_val = found_env_vars[:is_pe] type = case is_pe_val when /yes|true/ then 'pe' when /no|false/ then 'foss' else raise "Invalid value for one of #{ENVIRONMENT_SPEC[:is_pe].join(' ,')}: #{is_pe_val}" end found_env_vars[:type] = type end if found_env_vars[:run_in_parallel] found_env_vars[:run_in_parallel] = found_env_vars[:run_in_parallel].split(',') end found_env_vars[:pe_version_file_win] = found_env_vars[:pe_version_file] found_env_vars end # Generates an OptionsHash of the environment variables of interest to Beaker # # @return [OptionsHash] The supported environment variables in an OptionsHash, # empty or nil environment variables are removed from the OptionsHash def calculate_env_vars found = Beaker::Options::OptionsHash.new found = found.merge(format_found_env_vars( collect_env_vars( ENVIRONMENT_SPEC ))) found[:answers] = select_env_by_regex('\\Aq_') found.delete_if {|key, value| value.nil? or value.empty? } found end # Return an OptionsHash of environment variables used in this run of Beaker # # @return [OptionsHash] The supported environment variables in an OptionsHash, # empty or nil environment variables are removed from the OptionsHash def env_vars @env ||= calculate_env_vars end # Generates an OptionsHash of preset values for arguments supported by Beaker # # @return [OptionsHash] The supported arguments in an OptionsHash def presets h = Beaker::Options::OptionsHash.new h.merge({ :project => 'Beaker', :department => 'unknown', :created_by => ENV['USER'] || ENV['USERNAME'] || 'unknown', :host_tags => {}, :openstack_api_key => ENV['OS_PASSWORD'], :openstack_username => ENV['OS_USERNAME'], :openstack_auth_url => "#{ENV['OS_AUTH_URL']}/tokens", :openstack_tenant => ENV['OS_TENANT_NAME'], :openstack_keyname => ENV['OS_KEYNAME'], :openstack_network => ENV['OS_NETWORK'], :openstack_region => ENV['OS_REGION'], :openstack_volume_support => ENV['OS_VOLUME_SUPPORT'] || true, :jenkins_build_url => nil, :validate => true, :configure => true, :log_level => 'info', :trace_limit => 10, :"master-start-curl-retries" => 120, :masterless => false, :options_file => nil, :type => 'pe', :provision => true, :preserve_hosts => 'never', :root_keys => false, :quiet => false, :project_root => File.expand_path(File.join(File.dirname(__FILE__), "../")), :xml_dir => 'junit', :xml_file => 'beaker_junit.xml', :xml_time => 'beaker_times.xml', :xml_time_enabled => false, :xml_stylesheet => 'junit.xsl', :default_log_prefix => 'beaker_logs', :log_dir => 'log', :log_sut_event => 'sut.log', :color => true, :dry_run => false, :test_tag_and => '', :test_tag_or => '', :test_tag_exclude => '', :timeout => 900, # 15 minutes :fail_mode => 'slow', :test_results_file => '', :accept_all_exit_codes => false, :timesync => false, :disable_iptables => false, :set_env => true, :disable_updates => true, :repo_proxy => false, :package_proxy => false, :add_el_extras => false, :epel_url => "http://dl.fedoraproject.org/pub/epel", :consoleport => 443, :pe_dir => '/opt/enterprise/dists', :pe_version_file => 'LATEST', :pe_version_file_win => 'LATEST-win', :host_env => {}, :host_name_prefix => nil, :ssh_env_file => '~/.ssh/environment', :profile_d_env_file => '/etc/profile.d/beaker_env.sh', :dot_fog => File.join(ENV['HOME'], '.fog'), :ec2_yaml => 'config/image_templates/ec2.yaml', :help => false, :collect_perf_data => 'none', :puppetdb_port_ssl => 8081, :puppetdb_port_nonssl => 8080, :puppetserver_port => 8140, :nodeclassifier_port => 4433, :cache_files_locally => false, :aws_keyname_modifier => rand(10 ** 10).to_s.rjust(10,'0'), # 10 digit random number string :run_in_parallel => [], :use_fog_credentials => true, :ssh => { :config => false, :verify_host_key => false, :auth_methods => ["publickey"], :port => 22, :forward_agent => true, :keys => ["#{ENV['HOME']}/.ssh/id_rsa"], :user_known_hosts_file => "#{ENV['HOME']}/.ssh/known_hosts", :keepalive => true } }) end end end end beaker-4.30.0/lib/beaker/options/subcommand_options_file_parser.rb000066400000000000000000000014331407603575700253430ustar00rootroot00000000000000module Beaker module Options #A set of functions to read options files module SubcommandOptionsParser def self.parse_options_file(options_file_path) result = OptionsHash.new if File.exist?(options_file_path) result = YAML.load_file(options_file_path) end result end # @return [OptionsHash, Hash] returns an empty OptionHash or loads subcommand options yaml # from disk def self.parse_subcommand_options(argv, options_file) result = OptionsHash.new if Beaker::Subcommands::SubcommandUtil.execute_subcommand?(argv[0]) return result if argv[0] == 'init' result = SubcommandOptionsParser.parse_options_file(options_file) end result end end end end beaker-4.30.0/lib/beaker/options/validator.rb000066400000000000000000000130411407603575700210500ustar00rootroot00000000000000module Beaker module Options class Validator VALID_FAIL_MODES = /stop|fast|slow/ VALID_PRESERVE_HOSTS = /always|onfail|onpass|never/ FRICTIONLESS_ROLE = 'frictionless' FRICTIONLESS_ADDITIONAL_ROLES = %w(master database dashboard console) # Determine is a given file exists and is a valid YAML file # @param [String] f The YAML file path to examine # @param [String] msg An options message to report in case of error # @raise [ArgumentError] Raise if file does not exist or is not valid YAML def check_yaml_file(f, msg = '') validator_error "#{f} does not exist (#{msg})" unless File.file?(f) begin YAML.load_file(f) rescue Beaker::Options::Parser::PARSE_ERROR => e validator_error "#{f} is not a valid YAML file (#{msg})\n\t#{e}" end end # Raises an ArgumentError with associated message # @param [String] msg The error message to be reported # @raise [ArgumentError] Takes the supplied message and raises it as an ArgumentError def validator_error(msg = '') raise ArgumentError, msg.to_s end # alias to keep old methods and functionality from throwing errors. alias_method :parser_error, :validator_error # Raises an ArgumentError if more than one default exists, # otherwise returns true or false if default is set. # # @param [Array] default list of host names # @return [true, false] def default_set?(default) if default.empty? return false elsif default.length > 1 validator_error "Only one host may have the role 'default', default roles assigned to #{default}" end true end # Raises an error if fail_mode is not a supported failure mode. # # @param [String] fail_mode Failure mode setting # @return [nil] Does not return anything def validate_fail_mode(fail_mode) #check for valid fail mode if fail_mode !~ VALID_FAIL_MODES validator_error "--fail-mode must be one of fast or slow, not '#{fail_mode}'" end end # Raises an error if hosts_setting is not a supported preserve hosts value. # # @param [String] hosts_setting Preserve hosts setting # @return [nil] Does not return anything def validate_preserve_hosts(hosts_setting) #check for valid preserve_hosts option if hosts_setting !~ VALID_PRESERVE_HOSTS validator_error("--preserve_hosts must be one of always, onfail, onpass or never, not '#{hosts_setting}'") end end # Raise an error if host does not have a platform defined. # # @param [::Beaker::Host] host A beaker host # @param [String] name Host name # @return [nil] Does not return anything def validate_platform(host, name) if !host['platform'] || host['platform'].empty? validator_error "Host #{name} does not have a platform specified" end end # Raise an error if an item exists in both the include and exclude lists. # # @note see test tagging logic at {Beaker::DSL::TestTagging} module # # @param [Array] tags_and included items # @param [Array] tags_exclude excluded items # @return [nil] Does not return anything def validate_test_tags(tags_and, tags_or, tags_exclude) if tags_and.length > 0 && tags_or.length > 0 validator_error "cannot have values for both test tagging operands (AND and OR)" end tags_and.each do |included_tag| # select items from exclude set that match included_tag # no match is an empty list/array/[] if tags_exclude.select { |ex| ex == included_tag } != [] validator_error "tag '#{included_tag}' cannot be in both the included and excluded tag sets" end end end # Raises an error if role_array contains the frictionless role and conflicting roles. # # @param [Array] role_array List of roles # @raise [ArgumentError] Raises if role_array contains conflicting roles def validate_frictionless_roles(role_array) if role_array.include?(FRICTIONLESS_ROLE) and !(role_array & FRICTIONLESS_ADDITIONAL_ROLES).empty? validator_error "Only agent nodes may have the role 'frictionless'." end end # Raise an error if the master count is incorrect. # # @param [Integer] count Count of roles with 'master' # @return [nil] Nothing is returned # @raise [ArgumentError] Raises if master count is greater than 1 def validate_master_count(count) if count > 1 validator_error("Only one host/node may have the role 'master'.") end end # Raise an error if file_list is empty # # @param [Array] file_list list of files # @param [String] path file path to report in error # @raise [ArgumentError] Raises if file_list is empty def validate_files(file_list, path) if file_list.empty? validator_error("No files found for path: '#{path}'") end end # Raise an error if path is not a valid file or directory # # @param [String] path File path # @raise [ArgumentError] Raises if path is not a valid file or directory def validate_path(path) if !File.file?(path) && !File.directory?(path) validator_error("#{path} used as a file option but is not a file or directory!") end end end end end beaker-4.30.0/lib/beaker/perf.rb000066400000000000000000000127541407603575700163360ustar00rootroot00000000000000module Beaker # The Beaker Perf class. A single instance is created per Beaker run. class Perf PERF_PACKAGES = ['sysstat'] # SLES does not treat sysstat as a service that can be started PERF_SUPPORTED_PLATFORMS = /debian|ubuntu|redhat|centos|oracle|scientific|fedora|el|eos|cumulus|opensuse|sles/ PERF_START_PLATFORMS = /debian|ubuntu|redhat|centos|oracle|scientific|fedora|el|eos|cumulus/ # Create the Perf instance and runs setup_perf_on_host on all hosts if --collect-perf-data # was used as an option on the Baker command line invocation. Instances of this class do not # hold state and its methods are helpers for remotely executing tasks for performance data # gathering with sysstat/sar # # @param [Array] hosts All from the configuration # @param [Hash] options Options to alter execution # @return [void] def initialize( hosts, options ) @hosts = hosts @options = options @logger = options[:logger] @perf_timestamp = Time.now @hosts.map { |h| setup_perf_on_host(h) } end # Install sysstat if required and perform any modifications needed to make sysstat work. # @param [Host] host The host we are working with # @return [void] def setup_perf_on_host(host) @logger.perf_output("Setup perf on host: " + host) # Install sysstat if required if host['platform'] =~ PERF_SUPPORTED_PLATFORMS PERF_PACKAGES.each do |pkg| if not host.check_for_package pkg host.install_package pkg end end else @logger.perf_output("Perf (sysstat) not supported on host: " + host) end if host['platform'] =~ /debian|ubuntu|cumulus/ @logger.perf_output("Modify /etc/default/sysstat on Debian and Ubuntu platforms") host.exec(Command.new('sed -i s/ENABLED=\"false\"/ENABLED=\"true\"/ /etc/default/sysstat')) elsif host['platform'] =~ /opensuse|sles/ @logger.perf_output("Creating symlink from /etc/sysstat/sysstat.cron to /etc/cron.d") host.exec(Command.new('ln -s /etc/sysstat/sysstat.cron /etc/cron.d'),:acceptable_exit_codes => [0,1]) end if @options[:collect_perf_data] =~ /aggressive/ @logger.perf_output("Enabling aggressive sysstat polling") if host['platform'] =~ /debian|ubuntu/ host.exec(Command.new('sed -i s/5-55\\\/10/*/ /etc/cron.d/sysstat')) elsif host['platform'] =~ /centos|el|fedora|oracle|redhat|scientific/ host.exec(Command.new('sed -i s/*\\\/10/*/ /etc/cron.d/sysstat')) end end if host['platform'] =~ PERF_START_PLATFORMS # SLES doesn't need this step host.exec(Command.new('service sysstat start')) end end # Iterate over all hosts, calling get_perf_data # @return [void] def print_perf_info() @perf_end_timestamp = Time.now @hosts.map { |h| get_perf_data(h, @perf_timestamp, @perf_end_timestamp) } end # If host is a supported (ie linux) platform, generate a performance report # @param [Host] host The host we are working with # @param [Time] perf_start The beginning time for the SAR report # @param [Time] perf_end The ending time for the SAR report # @return [void] The report is sent to the logging output def get_perf_data(host, perf_start, perf_end) @logger.perf_output("Getting perf data for host: " + host) if host['platform'] =~ PERF_SUPPORTED_PLATFORMS # All flavours of Linux if not @options[:collect_perf_data] =~ /aggressive/ host.exec(Command.new("sar -A -s #{perf_start.strftime("%H:%M:%S")} -e #{perf_end.strftime("%H:%M:%S")}"),:acceptable_exit_codes => [0,1,2]) end if (defined? @options[:graphite_server] and not @options[:graphite_server].nil?) and (defined? @options[:graphite_perf_data] and not @options[:graphite_perf_data].nil?) export_perf_data_to_graphite(host) end else @logger.perf_output("Perf (sysstat) not supported on host: " + host) end end # Send performance report numbers to an external Graphite instance # @param [Host] host The host we are working with # @return [void] The report is sent to the logging output def export_perf_data_to_graphite(host) @logger.perf_output("Sending data to Graphite server: " + @options[:graphite_server]) data = JSON.parse(host.exec(Command.new("sadf -j -- -A"),:silent => true).stdout) hostname = host['vmhostname'].split('.')[0] data['sysstat']['hosts'].each do |host| host['statistics'].each do |poll| timestamp = DateTime.parse(poll['timestamp']['date'] + ' ' + poll['timestamp']['time']).to_time.to_i poll.keys.each do |stat| case stat when 'cpu-load-all' poll[stat].each do |s| s.keys.each do |k| next if k == 'cpu' socket = TCPSocket.new(@options[:graphite_server], 2003) socket.puts "#{@options[:graphite_perf_data]}.#{hostname}.cpu.#{s['cpu']}.#{k} #{s[k]} #{timestamp}" socket.close end end when 'memory' poll[stat].keys.each do |s| socket = TCPSocket.new(@options[:graphite_server], 2003) socket.puts "#{@options[:graphite_perf_data]}.#{hostname}.memory.#{s} #{poll[stat][s]} #{timestamp}" socket.close end end end end end end end end beaker-4.30.0/lib/beaker/platform.rb000066400000000000000000000114421407603575700172170ustar00rootroot00000000000000module Beaker # This class create a Platform object inheriting from String. It supports # all String methods while adding several platform-specific use cases. class Platform < String # Supported platforms PLATFORMS = /^(alpine|huaweios|cisco_nexus|cisco_ios_xr|(free|open)bsd|osx|centos|fedora|debian|oracle|redhat|redhatfips|scientific|opensuse|sles|ubuntu|windows|solaris|aix|archlinux|el|eos|cumulus|f5|netscaler)\-.+\-.+$/ # Platform version numbers vs. codenames conversion hash PLATFORM_VERSION_CODES = { :debian => { "bullseye" => "11", "buster" => "10", "stretch" => "9", "jessie" => "8", "wheezy" => "7", "squeeze" => "6", }, :ubuntu => { "focal" => "2004", "eoan" => "1910", "disco" => "1904", "cosmic" => "1810", "bionic" => "1804", "artful" => "1710", "zesty" => "1704", "yakkety" => "1610", "xenial" => "1604", "wily" => "1510", "vivid" => "1504", "utopic" => "1410", "trusty" => "1404", "saucy" => "1310", "raring" => "1304", "quantal" => "1210", "precise" => "1204", "lucid" => "1004", }, :osx => { "highsierra" => "1013", "sierra" => "1012", "elcapitan" => "1011", "yosemite" => "1010", "mavericks" => "109", } } # A string with the name of the platform. attr_reader :variant # A string with the version number of the platform. attr_reader :version # A string with the codename of the platform+version, nil on platforms # without codenames. attr_reader :codename # A string with the cpu architecture of the platform. attr_reader :arch # Creates the Platform object. Checks to ensure that the platform String # provided meets the platform formatting rules. Platforms name must be of # the format /^OSFAMILY-VERSION-ARCH.*$/ where OSFAMILY is one of: # * huaweios # * cisco_nexus # * cisco_ios_xr # * freebsd # * openbsd # * osx # * centos # * fedora # * debian # * oracle # * redhat # * redhatfips # * scientific # * opensuse # * sles # * ubuntu # * windows # * solaris # * aix # * el # * cumulus # * f5 # * netscaler # * archlinux def initialize(name) if name !~ PLATFORMS raise ArgumentError, "Unsupported platform name #{name}" end super @variant, version, @arch = self.split('-', 3) codename_version_hash = PLATFORM_VERSION_CODES[@variant.to_sym] @version = version @codename = nil if codename_version_hash if codename_version_hash[version] @codename = version @version = codename_version_hash[version] else version = version.delete('.') version_codename_hash = codename_version_hash.invert @codename = version_codename_hash[version] end end end # Returns array of attributes to allow single line assignment to local # variables in DSL and test case methods. def to_array return @variant, @version, @arch, @codename end # Returns the platform string with the platform version as a codename. If no conversion is # necessary then the original, unchanged platform String is returned. # @example Platform.new('debian-7-xxx').with_version_codename == 'debian-wheezy-xxx' # @return [String] the platform string with the platform version represented as a codename def with_version_codename version_array = [@variant, @version, @arch] if @codename version_array = [@variant, @codename, @arch] end return version_array.join('-') end # Returns the platform string with the platform version as a number. If no conversion is necessary # then the original, unchanged platform String is returned. # @example Platform.new('debian-wheezy-xxx').with_version_number == 'debian-7-xxx' # @return [String] the platform string with the platform version represented as a number def with_version_number [@variant, @version, @arch].join('-') end if RUBY_VERSION =~ /^1\.9/ def init_with(coder) coder.map.each do |ivar, value| instance_variable_set("@#{ivar}", value) end replace("#{@variant}-#{@version}-#{@arch}") end end end end beaker-4.30.0/lib/beaker/result.rb000066400000000000000000000033741407603575700167160ustar00rootroot00000000000000module Beaker class Result attr_accessor :host, :cmd, :exit_code, :stdout, :stderr, :output, :raw_stdout, :raw_stderr, :raw_output def initialize(host, cmd) @host = host @cmd = cmd @stdout = '' @stderr = '' @output = '' @exit_code = nil end # Ruby assumes chunked data (like something it receives from Net::SSH) # to be binary (ASCII-8BIT). We need to gather all chunked data and then # re-encode it as the default encoding it assumes for external text # (ie our test files and the strings they're trying to match Net::SSH's # output from) # This is also the lowest overhead place to normalize line endings, IIRC def finalize! @raw_stdout = @stdout @stdout = normalize_line_endings( convert( @stdout ) ) @raw_stderr = @stderr @stderr = normalize_line_endings( convert( @stderr ) ) @raw_output = @output @output = normalize_line_endings( convert( @output ) ) end def normalize_line_endings string return string.gsub(/\r\n?/, "\n") end def convert string # Remove invalid and undefined UTF-8 character encodings string.to_s.force_encoding('UTF-8') string.to_s.chars.select{|i| i.valid_encoding?}.join end def log(logger) logger.debug "Exited: #{exit_code}" unless exit_code == 0 or !exit_code end def formatted_output(limit=10) @output.split("\n").last(limit).collect {|x| "\t" + x}.join("\n") end def exit_code_in?(range) range.include?(@exit_code) end def success? exit_code == 0 end end class NullResult < Result def initialize(host, cmd) super(host, cmd) @exit_code = 0 end end end beaker-4.30.0/lib/beaker/shared.rb000066400000000000000000000007611407603575700166430ustar00rootroot00000000000000[ 'repetition', 'error_handler', 'host_manager', 'timed', 'semvar', 'options_resolver', 'fog_credentials'].each do |lib| require "beaker/shared/#{lib}" end module Beaker module Shared include Beaker::Shared::ErrorHandler include Beaker::Shared::HostManager include Beaker::Shared::Repetition include Beaker::Shared::Timed include Beaker::Shared::Semvar include Beaker::Shared::OptionsResolver include Beaker::Shared::FogCredentials end end include Beaker::Shared beaker-4.30.0/lib/beaker/shared/000077500000000000000000000000001407603575700163125ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/shared/error_handler.rb000066400000000000000000000005101407603575700214610ustar00rootroot00000000000000module Beaker module Shared module ErrorHandler def report_and_raise(logger, e, msg) logger.error "Failed: errored in #{msg}" logger.error(e.inspect) bt = e.backtrace logger.pretty_backtrace(bt).each_line do |line| logger.error(line) end raise e end end end end beaker-4.30.0/lib/beaker/shared/fog_credentials.rb000066400000000000000000000052031407603575700217670ustar00rootroot00000000000000require 'stringify-hash' module Beaker module Shared # A set of functions to read .fog files module FogCredentials # Constructs ArgumentError with common phrasing for #get_fog_credentials errors # # @param path [String] path to offending file # @param from_env [String] if the path was overridden in ENV # @param reason [String] explanation for the failure # @return [ArgumentError] ArgumentError with preformatted message def fog_credential_error(path = nil, from_env = nil, reason = nil) message = "Failed loading credentials from .fog file" message << " '#{path}'" if path message << " #{from_env}" if from_env message << "." message << "Reason: #{reason}" if reason ArgumentError.new(message) end # Load credentials from a .fog file # # @note Loaded .fog files may use symbols for keys. # Although not clearly documented, it is valid: # https://www.rubydoc.info/gems/fog-core/1.42.0/Fog#credential-class_method # https://github.com/fog/fog-core/blob/7865ef77ea990fd0d085e49c28e15957b7ce0d2b/spec/utils_spec.rb#L11 # # @param fog_file_path [String] dot fog path. Overridden by ENV["FOG_RC"] # @param credential_group [String, Symbol] Credential group to use. Overridden by ENV["FOG_CREDENTIAL"] # @return [StringifyHash] credentials stored in fog_file_path # @raise [ArgumentError] when the credentials cannot be loaded, describing the reson def get_fog_credentials(fog_file_path = '~/.fog', credential_group = :default) # respect file location from env if ENV["FOG_RC"] fog_file_path = ENV["FOG_RC"] from_env = ' set in ENV["FOG_RC"]' end begin fog = YAML.load_file(fog_file_path) rescue Psych::SyntaxError, Errno::ENOENT => e raise fog_credential_error fog_file_path, from_env, "(#{e.class}) #{e.message}" end if fog == false # YAML.load => false for empty file raise fog_credential_error fog_file_path, from_env, "is empty." end # transparently support symbols or strings for keys fog = StringifyHash.new.merge!(fog) # respect credential from env # @note ENV must be a string, e.g. "default" not ":default" if ENV["FOG_CREDENTIAL"] credential_group = ENV["FOG_CREDENTIAL"].to_sym end if not fog[credential_group] raise fog_credential_error fog_file_path, from_env, "could not load the specified credential group '#{credential_group}'." end fog[credential_group] end end end end beaker-4.30.0/lib/beaker/shared/host_manager.rb000066400000000000000000000150211407603575700213050ustar00rootroot00000000000000module Beaker module Shared #Methods for managing Hosts. #- selecting hosts by role (Symbol or String) #- selecting hosts by name (String) #- adding additional method definitions for selecting by role #- executing blocks of code against selected sets of hosts module HostManager #Find hosts from a given array of hosts that all have the desired role. #@param [Array] hosts The hosts to examine #@param [String] desired_role The hosts returned will have this role in their roles list #@return [Array] The hosts that have the desired role in their roles list def hosts_with_role(hosts, desired_role = nil) hosts.select do |host| desired_role.nil? or host['roles'].include?(desired_role.to_s) end end #Find hosts from a given array of hosts that all have the desired name, match against host name, #vmhostname and ip (the three valid ways to identify an individual host) #@param [Array] hosts The hosts to examine #@param [String] name The hosts returned will have this name/vmhostname/ip #@return [Array] The hosts that have the desired name/vmhostname/ip def hosts_with_name(hosts, name = nil) hosts.select do |host| name.nil? or host.name =~ /\A#{name}/ or host[:vmhostname] =~ /\A#{name}/ or host[:ip] =~ /\A#{name}/ end end #Find a single host with the role provided. Raise an error if more than one host is found to have the #provided role. #@param [Array] hosts The hosts to examine #@param [String] role The host returned will have this role in its role list #@return [Host] The single host with the desired role in its roles list #@raise [ArgumentError] Raised if more than one host has the given role defined, if no host has the # role defined, or if role = nil since hosts_with_role(nil) returns all hosts. def only_host_with_role(hosts, role) raise ArgumentError, "role cannot be nil." if role.nil? a_host = hosts_with_role(hosts, role) case when a_host.length == 0 raise ArgumentError, "There should be one host with #{role} defined!" when a_host.length > 1 host_string = ( a_host.map { |host| host.name } ).join( ', ') raise ArgumentError, "There should be only one host with #{role} defined, but I found #{a_host.length} (#{host_string})" end a_host.first end # Find at most a single host with the role provided. Raise an error if # more than one host is found to have the provided role. # @param [Array] hosts The hosts to examine # @param [String] role The host returned will have this role in its role list # @return [Host] The single host with the desired role in its roles list # or nil if no host is found # @raise [ArgumentError] Raised if more than one host has the given role defined, # or if role = nil since hosts_with_role(nil) returns all hosts. def find_at_most_one_host_with_role(hosts, role) raise ArgumentError, "role cannot be nil." if role.nil? role_hosts = hosts_with_role(hosts, role) host_with_role = nil case role_hosts.length when 0 when 1 host_with_role = role_hosts[0] else host_string = ( role_hosts.map { |host| host.name } ).join( ', ') raise ArgumentError, "There should be only one host with #{role} defined, but I found #{role_hosts.length} (#{host_string})" end host_with_role end # Execute a block selecting the hosts that match with the provided criteria # # @param [Array, Host] hosts The host or hosts to run the provided block against # @param [String, Symbol] filter Optional filter to apply to provided hosts - limits by name or role # @param [Hash{Symbol=>String}] opts # @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel. # @param [Block] block This method will yield to a block of code passed by the caller # # @todo (beaker3.0:BKR-571): simplify return types to Array only # # @return [Array, Result, nil] If an array of hosts has been # passed (after filtering), then either an array of results is returned # (if the array is non-empty), or nil is returned (if the array is empty). # Else, a result object is returned. If filtering makes it such that only # one host is left, then it's passed as a host object (not in an array), # and thus a result object is returned. def run_block_on hosts = [], filter = nil, opts = {}, &block result = nil block_hosts = hosts #the hosts to apply the block to after any filtering if filter if not hosts.empty? block_hosts = hosts_with_role(hosts, filter) #check by role if block_hosts.empty? block_hosts = hosts_with_name(hosts, filter) #check by name end if block_hosts.length == 1 #we only found one matching host, don't need it wrapped in an array block_hosts = block_hosts.pop end else raise ArgumentError, "Unable to sort for #{filter} type hosts when provided with [] as Hosts" end end if block_hosts.is_a? Array if block_hosts.length > 0 if run_in_parallel? opts # Pass caller[1] - the line that called block_on - for logging purposes. result = block_hosts.map.each_in_parallel(caller[1]) do |h| run_block_on h, &block end hosts.each{|host| host.close}# For some reason, I have to close the SSH connection # after spawning a process and running commands on a host, # or else it gets into a broken state for the next call. else result = block_hosts.map do |h| run_block_on h, &block end end else # there are no matching hosts to execute against # should warn here # check if logger is defined in this context if ( cur_logger = (logger || @logger ) ) cur_logger.info "Attempting to execute against an empty array of hosts (#{hosts}, filtered to #{block_hosts}), no execution will occur" end end else result = yield block_hosts end result end end end end beaker-4.30.0/lib/beaker/shared/options_resolver.rb000066400000000000000000000037221407603575700222570ustar00rootroot00000000000000module Beaker module Shared # Methods for parsing options. module OptionsResolver # parses local and global options to determine if a particular mode should # be run in parallel. typically, local_options will specify a true/false # value, while global_options will specify an array of mode names that should # be run in parallel. the value specified in local_options will take precedence # over the values specified in global_options. # @param [Hash] local_options local options for running in parallel # @option local_options [Boolean] :run_in_parallel flag for running in parallel # @param [Hash] global_options global options for running in parallel # @option global_options [Array] :run_in_parallel list of modes to run in parallel # @param [String] mode the mode we want to query global_options for # @return [Boolean] true if the specified mode is in global_options and :run_in_parallel in local_options is not false, # or if :run_in_parallel in local_options is true, false otherwise # @example # run_in_parallel?({:run_in_parallel => true}) # -> will return true # # run_in_parallel?({:run_in_parallel => true}, {:run_in_parallel => ['install','configure']}, 'install') # -> will return true # # run_in_parallel?({:run_in_parallel => false}, {:run_in_parallel => ['install','configure']}, 'install') # -> will return false def run_in_parallel?(local_options=nil, global_options=nil, mode=nil) run_in_parallel = local_options[:run_in_parallel] unless local_options.nil? if !run_in_parallel.nil? && run_in_parallel.is_a?(Array) run_in_parallel = false end if run_in_parallel.nil? && global_options && global_options[:run_in_parallel].is_a?(Array) run_in_parallel = global_options[:run_in_parallel].include?(mode) end run_in_parallel end end end end beaker-4.30.0/lib/beaker/shared/repetition.rb000066400000000000000000000015061407603575700210230ustar00rootroot00000000000000module Beaker module Shared module Repetition def repeat_for seconds, &block # do not peg CPU if &block takes less than 1 second repeat_for_and_wait seconds, 1, &block end def repeat_for_and_wait seconds, wait, &block timeout = Time.now + seconds done = false until done or timeout < Time.now do done = block.call sleep wait unless done end return done end def repeat_fibonacci_style_for attempts, &block done = false attempt = 1 last_wait, wait = 0, 1 while not done and attempt <= attempts do done = block.call attempt += 1 sleep wait unless done last_wait, wait = wait, last_wait + wait end return done end end end end beaker-4.30.0/lib/beaker/shared/semvar.rb000066400000000000000000000065111407603575700201370ustar00rootroot00000000000000module Beaker module Shared module Semvar #Is semver-ish version a less than semver-ish version b #@param [String] a A version of the from '\d.\d.\d.*' #@param [String] b A version of the form '\d.\d.\d.*' #@return [Boolean] true if a is less than b, otherwise return false # #@note This has been updated for our current versioning scheme. #@note 2019.5.0 is greater than 2019.5.0-rc0 #@note 2019.5.0-rc0-1-gabc1234 is greater than 2019.5.0-rc0 #@note 2019.5.0-rc1 is greater than 2019.5.0-rc0-1-gabc1234 #@note 2019.5.0-1-gabc1234 is greater than 2019.5.0 def version_is_less a, b a_nums = a.split('-')[0].split('.') b_nums = b.split('-')[0].split('.') (0...a_nums.length).each do |i| if i < b_nums.length if a_nums[i].to_i < b_nums[i].to_i return true elsif a_nums[i].to_i > b_nums[i].to_i return false end else return false end end #checks all dots, they are equal so examine the rest a_rest = a.split('-').drop(1) a_is_release = a_rest.empty? a_is_rc = !a_is_release && !!(a_rest[0] =~ /rc\d+/) b_rest = b.split('-').drop(1) b_is_release = b_rest.empty? b_is_rc = !b_is_release && !!(b_rest[0] =~ /rc\d+/) if a_is_release && b_is_release # They are equal return false elsif !a_is_release && !b_is_release a_next = a_rest.shift b_next = b_rest.shift if a_is_rc && b_is_rc a_rc = a_next.gsub('rc','').to_i b_rc = b_next.gsub('rc','').to_i if a_rc < b_rc return true elsif a_rc > b_rc return false else a_next = a_rest.shift b_next = b_rest.shift if a_next && b_next return a_next.to_i < b_next.to_i else # If a has nothing after -rc#, it is a tagged RC and # b must be a later build after this tag. return a_next.nil? end end else # If one of them is not an rc (and also not a release), # that one is a post-release build. So if a is the RC, it is less. return a_is_rc end else return (b_is_release && a_is_rc) || (a_is_release && !b_is_rc) end end # Gets the max semver version from a list of them # @param [Array] versions List of versions to get max from # @param [String] default Default version if list is nil or empty # # @note nil values will be skipped # @note versions parameter will be copied so that the original # won't be tampered with # # @return [String, nil] the max string out of the versions list or the # default value if the list is faulty, which can either be set or nil def max_version(versions, default=nil) return default if !versions || versions.empty? versions_copy = versions.dup highest = versions_copy.shift versions_copy.each do |version| next if !version highest = version if version_is_less(highest, version) end highest end end end end beaker-4.30.0/lib/beaker/shared/timed.rb000066400000000000000000000002741407603575700177440ustar00rootroot00000000000000module Beaker module Shared module Timed def run_and_report_duration &block start = Time.now block.call Time.now - start end end end end beaker-4.30.0/lib/beaker/ssh_connection.rb000066400000000000000000000331211407603575700204050ustar00rootroot00000000000000require 'socket' require 'timeout' require 'net/scp' module Beaker class SshConnection attr_accessor :logger attr_accessor :ip, :vmhostname, :hostname, :ssh_connection_preference SUPPORTED_CONNECTION_METHODS = [:ip, :vmhostname, :hostname] RETRYABLE_EXCEPTIONS = [ SocketError, Timeout::Error, Errno::ETIMEDOUT, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENETUNREACH, Net::SSH::Exception, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ChannelRequestFailed, Net::SSH::ChannelOpenFailed, IOError, ] def initialize name_hash, user = nil, ssh_opts = {}, options = {} @vmhostname = name_hash[:vmhostname] @ip = name_hash[:ip] @hostname = name_hash[:hostname] @user = user @ssh_opts = ssh_opts @logger = options[:logger] @options = options @ssh_connection_preference = @options[:ssh_connection_preference] end def self.connect name_hash, user = 'root', ssh_opts = {}, options = {} connection = new name_hash, user, ssh_opts, options connection.connect connection end # Setup and return the ssh connection object # # @note For more information about Net::SSH library, check out these docs: # - {https://net-ssh.github.io/net-ssh/ Base Net::SSH docs} # - {http://net-ssh.github.io/net-ssh/Net/SSH.html#method-c-start Net::SSH.start method docs} # - {https://net-ssh.github.io/net-ssh/Net/SSH/Connection/Session.html Net::SSH::Connection::Session class docs} # # @param [String] host hostname of the machine to connect to # @param [String] user username to login to the host as # @param [Hash{Symbol=>String}] ssh_opts Options hash passed directly to Net::SSH.start method # @param [Hash{Symbol=>String}] options Options hash to control method conditionals # @option options [Integer] :max_connection_tries Limit the number of connection start # tries to this number (default: 11) # @option options [Boolean] :silent Stops logging attempt failure messages if set to true # (default: true) # # @return [Net::SSH::Connection::Session] session returned from Net::SSH.start method def connect_block host, user, ssh_opts, options try = 1 last_wait = 2 wait = 3 max_connection_tries = options[:max_connection_tries] || 11 begin @logger.debug "Attempting ssh connection to #{host}, user: #{user}, opts: #{ssh_opts}" # Work around net-ssh 6+ incompatibilities if ssh_opts.include?(:strict_host_key_checking) && (Net::SSH::Version::CURRENT.major > 5) strict_host_key_checking = ssh_opts.delete(:strict_host_key_checking) unless ssh_opts[:verify_host_key].is_a?(Symbol) ssh_opts[:verify_host_key] ||= strict_host_key_checking ? :always : :never end end Net::SSH.start(host, user, ssh_opts) rescue *RETRYABLE_EXCEPTIONS => e if try <= max_connection_tries @logger.warn "Try #{try} -- Host #{host} unreachable: #{e.class.name} - #{e.message}" unless options[:silent] @logger.warn "Trying again in #{wait} seconds" unless options[:silent] sleep wait (last_wait, wait) = wait, last_wait + wait try += 1 retry else @logger.warn "Failed to connect to #{host}, after #{try} attempts" unless options[:silent] nil end end end # Connect to the host, creating a new connection if required # # @param [Hash{Symbol=>String}] options Options hash to control method conditionals # @option options [Integer] :max_connection_tries {#connect_block} option # @option options [Boolean] :silent {#connect_block} option def connect options = {} # Try three ways to connect to host (vmhostname, ip, hostname) # Try each method in turn until we succeed methods = @ssh_connection_preference.dup while (not @ssh) && (not methods.empty?) do unless instance_variable_get("@#{methods[0]}").nil? if SUPPORTED_CONNECTION_METHODS.include?(methods[0]) @ssh = connect_block(instance_variable_get("@#{methods[0].to_s}"), @user, @ssh_opts, options) else @logger.warn "Beaker does not support #{methods[0]} to SSH to host, trying next available method." @ssh_connection_preference.delete(methods[0]) end else @logger.warn "Skipping #{methods[0]} method to ssh to host as its value is not set. Refer to https://github.com/puppetlabs/beaker/tree/master/docs/how_to/ssh_connection_preference.md to remove this warning" end methods.shift end unless @ssh @logger.error "Failed to connect to #{@hostname}, attempted #{@ssh_connection_preference.join(', ')}" raise RuntimeError, "Cannot connect to #{@hostname}" end @ssh end # closes this SshConnection def close begin if @ssh and not @ssh.closed? @ssh.close else @logger.warn("ssh.close: connection is already closed, no action needed") end rescue *RETRYABLE_EXCEPTIONS => e @logger.warn "Attemped ssh.close, (caught #{e.class.name} - #{e.message})." rescue => e @logger.warn "ssh.close threw unexpected Error: #{e.class.name} - #{e.message}. Shutting down, and re-raising error below" @ssh.shutdown! raise e ensure @ssh = nil @logger.debug("ssh connection to #{@hostname} has been terminated") end end # Wait for the ssh connection to fail, returns true on connection failure and false otherwise # @param [Hash{Symbol=>String}] options Options hash to control method conditionals # @option options [Boolean] :pty Should we request a terminal when attempting # to send a command over this connection? # @option options [String] :stdin Any input to be sent along with the command # @param [IO] stdout_callback An IO stream to send connection stdout to, defaults to nil # @param [IO] stderr_callback An IO stream to send connection stderr to, defaults to nil # @return [Boolean] true if connection failed, false otherwise def wait_for_connection_failure options = {}, stdout_callback = nil, stderr_callback = stdout_callback try = 1 last_wait = 2 wait = 3 command = 'echo echo' #can be run on all platforms (I'm looking at you, windows) while try < 11 result = Result.new(@hostname, command) begin @logger.notify "Waiting for connection failure on #{@hostname} (attempt #{try}, try again in #{wait} second(s))" @logger.debug("\n#{@hostname} #{Time.new.strftime('%H:%M:%S')}$ #{command}") @ssh.open_channel do |channel| request_terminal_for( channel, command ) if options[:pty] channel.exec(command) do |terminal, success| raise Net::SSH::Exception.new("FAILED: to execute command on a new channel on #{@hostname}") unless success register_stdout_for terminal, result, stdout_callback register_stderr_for terminal, result, stderr_callback register_exit_code_for terminal, result process_stdin_for( terminal, options[:stdin] ) if options[:stdin] end end loop_tries = 0 #loop is actually loop_forever, so let it try 3 times and then quit instead of endless blocking @ssh.loop { loop_tries += 1 ; loop_tries < 4 } rescue *RETRYABLE_EXCEPTIONS => e @logger.debug "Connection on #{@hostname} failed as expected (#{e.class.name} - #{e.message})" close #this connection is bad, shut it down return true end slept = 0 stdout_callback.call("sleep #{wait} second(s): ") while slept < wait sleep slept stdout_callback.call('.') slept += 1 end stdout_callback.call("\n") (last_wait, wait) = wait, last_wait + wait try += 1 end false end def try_to_execute command, options = {}, stdout_callback = nil, stderr_callback = stdout_callback result = Result.new(@hostname, command) @ssh.open_channel do |channel| request_terminal_for( channel, command ) if options[:pty] channel.exec(command) do |terminal, success| raise Net::SSH::Exception.new("FAILED: to execute command on a new channel on #{@hostname}") unless success register_stdout_for terminal, result, stdout_callback register_stderr_for terminal, result, stderr_callback register_exit_code_for terminal, result process_stdin_for( terminal, options[:stdin] ) if options[:stdin] end end # Process SSH activity until we stop doing that - which is when our # channel is finished with... begin @ssh.loop rescue *RETRYABLE_EXCEPTIONS => e # this would indicate that the connection failed post execution, since the channel exec was successful @logger.warn "ssh channel on #{@hostname} received exception post command execution #{e.class.name} - #{e.message}" close end result.finalize! @logger.last_result = result result end # Execute a command on a host, ensuring a connection exists first # # @param [Hash{Symbol=>String}] options Options hash to control method conditionals # @option options [Integer] :max_connection_tries {#connect_block} option (passed through {#connect}) # @option options [Boolean] :silent {#connect_block} option (passed through {#connect}) def execute command, options = {}, stdout_callback = nil, stderr_callback = stdout_callback # ensure that we have a current connection object connect(options) try_to_execute(command, options, stdout_callback, stderr_callback) end def request_terminal_for channel, command channel.request_pty do |ch, success| if success @logger.debug "Allocated a PTY on #{@hostname} for #{command.inspect}" else raise Net::SSH::Exception.new("FAILED: could not allocate a pty when requested on " + "#{@hostname} for #{command.inspect}") end end end def register_stdout_for channel, output, callback = nil channel.on_data do |ch, data| callback[data] if callback output.stdout << data output.output << data end end def register_stderr_for channel, output, callback = nil channel.on_extended_data do |ch, type, data| if type == 1 callback[data] if callback output.stderr << data output.output << data end end end def register_exit_code_for channel, output channel.on_request("exit-status") do |ch, data| output.exit_code = data.read_long end end def process_stdin_for channel, stdin # queue stdin data, force it to packets, and signal eof: this # triggers action in many remote commands, notably including # 'puppet apply'. It must be sent at some point before the rest # of the action. channel.send_data stdin.to_s channel.process channel.eof! end def scp_to source, target, options = {} local_opts = options.dup if local_opts[:recursive].nil? local_opts[:recursive] = File.directory?(source) end local_opts[:chunk_size] ||= 16384 result = Result.new(@hostname, [source, target]) result.stdout = "\n" begin # This is probably windows with an environment variable so we need to # expand it. target = self.execute(%{echo "#{target}"}).output.strip.gsub('"','') if target.include?('%') @ssh.scp.upload! source, target, local_opts do |ch, name, sent, total| result.stdout << "\tcopying %s: %10d/%d\n" % [name, sent, total] end rescue => e logger.warn "#{e.class} error in scp'ing. Forcing the connection to close, which should " << "raise an error." close end # Setting these values allows reporting via result.log(test_name) result.stdout << " SCP'ed file #{source} to #{@hostname}:#{target}" # Net::Scp always returns 0, so just set the return code to 0. result.exit_code = 0 result.finalize! return result end def scp_from source, target, options = {} local_opts = options.dup if local_opts[:recursive].nil? local_opts[:recursive] = true end local_opts[:chunk_size] ||= 16384 result = Result.new(@hostname, [source, target]) result.stdout = "\n" begin # This is probably windows with an environment variable so we need to # expand it. source = self.execute(%{echo "#{source}"}).output.strip.gsub('"','') if source.include?('%') @ssh.scp.download! source, target, local_opts do |ch, name, sent, total| result.stdout << "\tcopying %s: %10d/%d\n" % [name, sent, total] end rescue => e logger.warn "#{e.class} error in scp'ing. Forcing the connection to close, which should " << "raise an error." close end # Setting these values allows reporting via result.log(test_name) result.stdout << " SCP'ed file #{@hostname}:#{source} to #{target}" # Net::Scp always returns 0, so just set the return code to 0. result.exit_code = 0 result.finalize! result end end end beaker-4.30.0/lib/beaker/subcommand.rb000066400000000000000000000241261407603575700175260ustar00rootroot00000000000000require "thor" require "fileutils" require "beaker/subcommands/subcommand_util" module Beaker class Subcommand < Thor SubcommandUtil = Beaker::Subcommands::SubcommandUtil attr_reader :cli def initialize(*args) super FileUtils.mkdir_p(SubcommandUtil::CONFIG_DIR) FileUtils.touch(SubcommandUtil::SUBCOMMAND_OPTIONS) unless SubcommandUtil::SUBCOMMAND_OPTIONS.exist? FileUtils.touch(SubcommandUtil::SUBCOMMAND_STATE) unless SubcommandUtil::SUBCOMMAND_STATE.exist? @cli = Beaker::CLI.new end # Options listed in this group 'Beaker run' are options that can be set on subcommands # but are not processed by the subcommand itself. They are passed through so that when # a Beaker::CLI object executes, it can pick up these options. Notably excluded from this # group are `help` and `version`. Please note that whenever the command_line_parser.rb is # updated, this list should also be updated as well. class_option :'options-file', :aliases => '-o', :type => :string, :group => 'Beaker run' class_option :helper, :type => :string, :group => 'Beaker run' class_option :'load-path', :type => :string, :group => 'Beaker run' class_option :tests, :aliases => '-t', :type => :string, :group => 'Beaker run' class_option :'pre-suite', :type => :string, :group => 'Beaker run' class_option :'post-suite', :type => :string, :group => 'Beaker run' class_option :'pre-cleanup', :type => :string, :group => 'Beaker run' class_option :'provision', :type => :boolean, :group => 'Beaker run' class_option :'preserve-hosts', :type => :string, :group => 'Beaker run' class_option :'preserve-state', :type => :boolean, :group => 'Beaker run' class_option :'root-keys', :type => :boolean, :group => 'Beaker run' class_option :keyfile, :type => :string, :group => 'Beaker run' class_option :timeout, :type => :string, :group => 'Beaker run' class_option :install, :aliases => '-i', :type => :string, :group => 'Beaker run' class_option :modules, :aliases => '-m', :type => :string, :group => 'Beaker run' class_option :quiet, :aliases => '-q', :type => :boolean, :group => 'Beaker run' class_option :color, :type => :boolean, :group => 'Beaker run' class_option :'color-host-output', :type => :boolean, :group => 'Beaker run' class_option :'log-level', :type => :string, :group => 'Beaker run' class_option :'log-prefix', :type => :string, :group => 'Beaker run' class_option :'dry-run', :type => :boolean, :group => 'Beaker run' class_option :'fail-mode', :type => :string, :group => 'Beaker run' class_option :'test-results-file', :type => :string, :group => 'Beaker run' class_option :ntp, :type => :boolean, :group => 'Beaker run' class_option :'repo-proxy', :type => :boolean, :group => 'Beaker run' class_option :'add-el-extras', :type => :boolean, :group => 'Beaker run' class_option :'package-proxy', :type => :string, :group => 'Beaker run' class_option :'validate', :type => :boolean, :group => 'Beaker run' class_option :'collect-perf-data', :type => :boolean, :group => 'Beaker run' class_option :'parse-only', :type => :boolean, :group => 'Beaker run' class_option :tag, :type => :string, :group => 'Beaker run' class_option :'exclude-tags', :type => :string, :group => 'Beaker run' class_option :'xml-time-order', :type => :boolean, :group => 'Beaker run' class_option :'debug-errors', :type => :boolean, :group => 'Beaker run' class_option :'exec_manual_tests', :type => :boolean, :group => 'Beaker run' class_option :'test-tag-exclude', :type => :string, :group => 'Beaker run' class_option :'test-tag-and', :type => :string, :group => 'Beaker run' class_option :'test-tag-or', :type => :string, :group => 'Beaker run' # The following are listed as deprecated in beaker --help, but needed now for # feature parity for beaker 3.x. class_option :xml, :type => :boolean, :group => "Beaker run" class_option :type, :type => :string, :group => "Beaker run" class_option :debug, :type => :boolean, :group => "Beaker run" desc "init BEAKER_RUN_OPTIONS", "Initializes the required configuration for Beaker subcommand execution" long_desc <<-LONGDESC 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 subcommand execution. Subsequent subcommand execution, such as `provision`, will result in beaker making modifications to this file as necessary. LONGDESC option :help, :type => :boolean, :hide => true method_option :hosts, :aliases => '-h', :type => :string, :required => true def init() if options[:help] invoke :help, [], ["init"] return end @cli.parse_options options_to_write = SubcommandUtil.sanitize_options_for_save(@cli.configured_options) @cli.logger.notify 'Writing configured options to disk' File.open(SubcommandUtil::SUBCOMMAND_OPTIONS, 'w') do |f| f.write(options_to_write.to_yaml) end @cli.logger.notify "Options written to #{SubcommandUtil::SUBCOMMAND_OPTIONS}" state = YAML::Store.new(SubcommandUtil::SUBCOMMAND_STATE) state.transaction do state['provisioned'] = false end end desc "provision", "Provisions the beaker systems under test(SUTs)" long_desc <<-LONGDESC Provisions hosts defined in your subcommand_options file. You can pass the --hosts flag here to override any hosts provided there. Really, you can pass most any beaker flag here to override. LONGDESC option :help, :type => :boolean, :hide => true def provision() if options[:help] invoke :help, [], ["provision"] return end state = YAML::Store.new(SubcommandUtil::SUBCOMMAND_STATE) if state.transaction { state['provisioned']} SubcommandUtil.error_with('Provisioned SUTs detected. Please destroy and reprovision.') end @cli.parse_options @cli.provision # Sanitize the hosts cleaned_hosts = SubcommandUtil.sanitize_options_for_save(@cli.combined_instance_and_options_hosts) # Update each host provisioned with a flag indicating that it no longer needs # provisioning cleaned_hosts.each do |host, host_hash| host_hash['provision'] = false end # should we only update the options here with the new host? Or update the settings # with whatever new flags may have been provided with provision? options_storage = YAML::Store.new(SubcommandUtil::SUBCOMMAND_OPTIONS) options_storage.transaction do @cli.logger.notify 'updating HOSTS key in subcommand_options' options_storage['HOSTS'] = cleaned_hosts options_storage['hosts_preserved_yaml_file'] = @cli.options[:hosts_preserved_yaml_file] end @cli.preserve_hosts_file state.transaction do state['provisioned'] = true end end desc 'exec FILE(S)/BEAKER_SUITE', 'execute directories, files, or beaker suites' long_desc <<-LONG_DESC 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 LONG_DESC option :help, :type => :boolean, :hide => true def exec(resource=nil) if options[:help] invoke :help, [], ["exec"] return end @cli.parse_options @cli.initialize_network_manager if !resource @cli.execute! return end beaker_suites = [:pre_suite, :tests, :post_suite, :pre_cleanup] resources = resource.split(',') paths = resources.map { |r| Pathname(r) } if paths.all?(&:exist?) # If we determine the resource is a valid file resource, then we empty # all the suites and run that file resource in the tests suite. In the # future, when we have the ability to have custom suites, we should change # this to run in a custom suite. You know, in the future. beaker_suites.each do |suite| @cli.options[suite] = [] end @cli.options[:tests] = paths.map do |path| if path.directory? Dir.glob("#{path}/**/*.rb") else path.to_s end end.flatten elsif resources.all? { |r| r =~ /^(pre-suite|tests|post-suite|pre-cleanup)$/ } # The regex match here is loose so that users can supply multiple suites, # such as `beaker exec pre-suite,tests`. beaker_suites.each do |suite| @cli.options[suite] = [] unless resource.gsub(/-/, '_').match(suite.to_s) end else raise ArgumentError, "Unable to parse #{resource} with beaker exec" end @cli.execute! if options['preserve-state'] @cli.logger.notify 'updating HOSTS key in subcommand_options' hosts = SubcommandUtil.sanitize_options_for_save(@cli.combined_instance_and_options_hosts) options_storage = YAML::Store.new(SubcommandUtil::SUBCOMMAND_OPTIONS) options_storage.transaction do options_storage['HOSTS'] = hosts end end end desc "destroy", "Destroys the provisioned VMs" long_desc <<-LONG_DESC Destroys the currently provisioned VMs LONG_DESC option :help, :type => :boolean, :hide => true def destroy() if options[:help] invoke :help, [], ["destroy"] return end state = YAML::Store.new(SubcommandUtil::SUBCOMMAND_STATE) unless state.transaction { state['provisioned']} SubcommandUtil.error_with('Please provision an environment') end @cli.parse_options @cli.options[:provision] = false @cli.initialize_network_manager @cli.network_manager.cleanup state.transaction { state.delete('provisioned') } end end end beaker-4.30.0/lib/beaker/subcommands/000077500000000000000000000000001407603575700173575ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/subcommands/subcommand_util.rb000066400000000000000000000052351407603575700230760ustar00rootroot00000000000000require 'json' require 'stringio' require 'yaml/store' require 'fileutils' module Beaker module Subcommands # Methods used in execution of Subcommands # - should we execute a subcommand? # - sanitize options for saving as json # - exit with a specific message # - capture stdout and stderr module SubcommandUtil CONFIG_DIR = ".beaker" SUBCOMMAND_OPTIONS = Pathname("#{CONFIG_DIR}/subcommand_options.yaml") SUBCOMMAND_STATE = Pathname("#{CONFIG_DIR}/.subcommand_state.yaml") PERSISTED_HOSTS = Pathname("#{CONFIG_DIR}/.hosts.yaml") PERSISTED_HYPERVISORS = Pathname("#{CONFIG_DIR}/.hypervisors.yaml") # These options should not be part of persisted subcommand state UNPERSISTED_OPTIONS = [:beaker_version, :command_line, :hosts_file, :logger, :password_prompt, :timestamp] def self.execute_subcommand?(arg0) return false if arg0.nil? (Beaker::Subcommand.instance_methods(false) << :help).include? arg0.to_sym end def self.prune_unpersisted(options) UNPERSISTED_OPTIONS.each do |unpersisted_key| options.each do |key, value| if key == unpersisted_key options.delete(key) elsif value.is_a?(Hash) options[key] = self.prune_unpersisted(value) unless value.empty? end end end options end def self.sanitize_options_for_save(options) # God help us, the YAML library won't stop adding tags to objects, so this # hack is a way to force the options into the basic object types so that # an eventual YAML.dump or .to_yaml call doesn't add tags. # Relevant stackoverflow: http://stackoverflow.com/questions/18178098/how-do-i-have-ruby-yaml-dump-a-hash-subclass-as-a-simple-hash JSON.parse(prune_unpersisted(options).to_json) end # Print a message to the console and exit with specified exit code, defaults to 1 # @param [String] msg the message to output # @param [Hash] options to specify exit code or output stack trace def self.error_with(msg, options={}) puts msg puts options[:stack_trace] if options[:stack_trace] exit_code = options[:exit_code] ? options[:exit_code] : 1 exit(exit_code) end # Execute a task but capture stdout and stderr to a buffer def self.with_captured_output begin old_stdout = $stdout.clone old_stderr = $stderr.clone $stdout = StringIO.new $stderr = StringIO.new yield ensure $stdout = old_stdout $stderr = old_stderr end end end end end beaker-4.30.0/lib/beaker/tasks/000077500000000000000000000000001407603575700161715ustar00rootroot00000000000000beaker-4.30.0/lib/beaker/tasks/quick_start.rb000066400000000000000000000067771407603575700210700ustar00rootroot00000000000000require 'beaker-hostgenerator' CONFIG_DIR = 'acceptance/config' VAGRANT = ['ubuntu1404-64default.mdcal-ubuntu1404-64af', '--hypervisor=vagrant', '--global-config={box_url=https://vagrantcloud.com/puppetlabs/boxes/ubuntu-14.04-64-nocm,box=puppetlabs/ubuntu-14.04-64-nocm}'] VMPOOLER = ['redhat7-64default.mdcal-redhat7-64af'] namespace :beaker_quickstart do desc 'Generate Default Beaker Host Config File, valid options are: vmpooler or vagrant.' task :gen_hosts, [:hypervisor] do |t, args| hosts_file = "#{CONFIG_DIR}/default_#{args[:hypervisor]}_hosts.yaml" if args[:hypervisor] == 'vagrant' cli = VAGRANT elsif args[:hypervisor] == 'vmpooler' cli = VMPOOLER else puts "No hypervisor provided, defaulting to vagrant." hosts_file = "#{CONFIG_DIR}/default_vagrant_hosts.yaml" cli = VAGRANT end FileUtils.mkdir_p("#{CONFIG_DIR}") # -p creates intermediate directories as required puts "About to run - beaker-hostgenerator #{cli.to_s.delete!('[]"')}" if !File.exist?(hosts_file) then puts "Writing default host config to file - #{hosts_file}" File.open(hosts_file, 'w') do |fh| fh.print(BeakerHostGenerator::CLI.new(cli).execute) end else puts "Not overwriting Host Config File: #{hosts_file} - it already exists." end end desc 'Generate Default Pre-Suite' task :gen_pre_suite do pre_suite_file = "acceptance/setup/default_pre_suite.rb" FileUtils.mkdir_p('acceptance/setup') # -p ignores when dir already exists if !File.exist?(pre_suite_file) then puts "Writing default pre_suite to file - #{pre_suite_file}" File.open(pre_suite_file, 'w') do |fh| fh.print('install_puppet') end else puts "Not overwriting Pre Suite File: #{pre_suite_file} - it already exists." end end desc 'Generate Default Smoke Test' task :gen_smoke_test do smoke_test_file = "acceptance/tests/default_smoke_test.rb" FileUtils.mkdir_p('acceptance/tests') # -p ignores when dir already exists if !File.exist?(smoke_test_file) then puts "Writing default smoke test to file - #{smoke_test_file}" File.open(smoke_test_file, 'w') do |fh| fh.print("test_name 'puppet install smoketest' do step 'puppet install smoketest: verify \\'puppet help\\' can be successfully called on all hosts' do hosts.each do |host| on host, puppet('help') end end end") end else puts "Not overwriting Smoke Test File: #{smoke_test_file} - it already exists." end end desc 'Run Default Smoke Test, after generating default host config and test files, valid options are: vmpooler or vagrant.' task :run_test, [:hypervisor] => ["beaker_quickstart:gen_hosts", 'beaker_quickstart:gen_pre_suite', 'beaker_quickstart:gen_smoke_test'] do |t, args| hypervisor = args[:hypervisor] ||='vagrant' system_args = Hash.new system_args[:hosts] = "acceptance/config/default_#{hypervisor}_hosts.yaml" system_args[:pre_suite] = 'acceptance/setup/default_pre_suite.rb' system_args[:tests] = 'acceptance/tests/default_smoke_test.rb' puts "About to run - #{beaker_command(system_args)}" system(beaker_command(system_args)) end end def beaker_command(system_args) cmd_parts = [] cmd_parts << "beaker" cmd_parts << "--hosts #{system_args[:hosts]}" cmd_parts << "--pre-suite #{system_args[:pre_suite]}" cmd_parts << "--tests #{system_args[:tests]}" cmd_parts.flatten.join(" ") end beaker-4.30.0/lib/beaker/tasks/rake_task.rb000066400000000000000000000071311407603575700204640ustar00rootroot00000000000000require 'rake/task_arguments' require 'rake/tasklib' require 'rake' require 'beaker' module Beaker module Tasks class RakeTask < ::Rake::TaskLib include ::Rake::DSL if defined?(::Rake::DSL) DEFAULT_ACCEPTANCE_ROOT = "./acceptance" COMMAND_OPTIONS = [:fail_mode, :hosts, :helper, :keyfile, :log_level, :options_file, :preserve_hosts, :tests, :type, :acceptance_root, :name] # iterates of acceptable params COMMAND_OPTIONS.each do |sym| attr_accessor(sym.to_sym) end # Sets up the predefine task checking # @param args [Array] First argument is always the name of the task # if no additonal arguments are defined such as parameters it will default to [:hosts,:type] def initialize(*args, &task_block) @name = args.shift || 'beaker:test' if args.empty? args = [:hosts,:type] end @acceptance_root = DEFAULT_ACCEPTANCE_ROOT @options_file = nil define(args, &task_block) end private # Run the task provided, implements the rake task interface # # @param verbose [bool] Defines wether to run in verbose mode or not def run_task(verbose) puts "Running task" check_for_beaker_type_config command = beaker_command puts command if verbose success = system(command) if fail_mode == "fast" && !success $stderr.puts "#{command} failed" exit $?.exitstatus end end # @private def define(args, &task_block) # Depending on the version of rake, either last_description or last_comment will be available. desc "Run Beaker Acceptance" unless (::Rake.application.respond_to?(:last_description) ? ::Rake.application.last_description : ::Rake .application.last_comment) task name, *args do |_, task_args| RakeFileUtils.__send__(:verbose, verbose) do task_block.call(*[self, task_args].slice(0, task_block.arity)) if task_block run_task verbose end end end # # If an options file exists in the acceptance path for the type given use it as a default options file # if no other options file is provided # def check_for_beaker_type_config if !@options_file && File.exists?("#{@acceptance_root}/.beaker-#{@type}.cfg") @options_file = File.join(@acceptance_root, ".beaker-#{@type}.cfg") end end # # Check for existence of ENV variables for test if !@tests is undef # def check_env_variables if File.exists?(File.join(DEFAULT_ACCEPTANCE_ROOT, 'tests')) @tests = File.join(DEFAULT_ACCEPTANCE_ROOT, 'tests') end @tests = ENV['TESTS'] || ENV['TEST'] if !@tests end # # Generate the beaker command to run beaker with all possible options passed # def beaker_command cmd_parts = [] cmd_parts << "beaker" cmd_parts << "--keyfile #{@keyfile}" if @keyfile cmd_parts << "--hosts #{@hosts}" if (@hosts!=nil && !@hosts.empty?) cmd_parts << "--tests #{tests}" if @tests cmd_parts << "--options-file #{@options_file}" if @options_file cmd_parts << "--type #{@type}" if @type cmd_parts << "--helper #{@helper}" if @helper cmd_parts << "--fail-mode #{@fail_mode}" if @fail_mode cmd_parts.flatten.join(" ") end end end end beaker-4.30.0/lib/beaker/tasks/test.rb000066400000000000000000000006251407603575700175000ustar00rootroot00000000000000require 'beaker/tasks/rake_task' Beaker::Tasks::RakeTask.new do |t,args| t.type = args[:type] t.hosts = args[:hosts] end desc "Run Beaker PE tests" Beaker::Tasks::RakeTask.new("beaker:test:pe",:hosts) do |t,args| t.type = 'pe' t.hosts = args[:hosts] end desc "Run Beaker Git tests" Beaker::Tasks::RakeTask.new("beaker:test:git",:hosts) do |t,args| t.type = 'git' t.hosts = args[:hosts] endbeaker-4.30.0/lib/beaker/test_case.rb000066400000000000000000000151111407603575700173420ustar00rootroot00000000000000[ 'host', 'dsl' ].each do |lib| require "beaker/#{lib}" end require 'tempfile' require 'benchmark' require 'stringio' require 'rbconfig' module Beaker # This class represents a single test case. A test case is necessarily # contained all in one file though may have multiple dependent examples. # They are executed in order (save for any teardown procs registered # through {Beaker::DSL::Structure#teardown}) and once completed # the status of the TestCase is saved. Instance readers/accessors provide # the test case access to various details of the environment and suite # the test case is running within. # # See {Beaker::DSL} for more information about writing tests # using the DSL. class TestCase include Beaker::DSL # The Exception raised by Ruby's STDLIB's test framework (Ruby 1.9) TEST_EXCEPTION_CLASS = ::MiniTest::Assertion # Necessary for implementing {Beaker::DSL::Helpers#confine}. # Assumed to be an array of valid {Beaker::Host} objects for # this test case. attr_accessor :hosts # Necessary for many methods in {Beaker::DSL}. Assumed to be # an instance of {Beaker::Logger}. attr_accessor :logger # Necessary for many methods in {Beaker::DSL::Helpers}. Assumed to be # a hash. attr_accessor :metadata # Necessary for {Beaker::DSL::Outcomes}. # Assumed to be an Array. attr_accessor :exports #The full log for this test attr_accessor :sublog #The result for the last command run attr_accessor :last_result # A Hash of 'product name' => 'version installed', only set when # products are installed via git or PE install steps. See the 'git' or # 'pe' directories within 'ROOT/setup' for examples. attr_reader :version # Parsed command line options. attr_reader :options # The path to the file which contains this test case. attr_reader :path # I don't know why this is here attr_reader :fail_flag # The user that is running this tests home directory, needed by 'net/ssh'. attr_reader :usr_home # A Symbol denoting the status of this test (:fail, :pending, # :skipped, :pass). attr_reader :test_status # The exception that may have stopped this test's execution. attr_reader :exception # @deprecated # The amount of time taken to execute the test. Unused, probably soon # to be removed or refactored. attr_reader :runtime # An Array of Procs to be called after test execution has stopped # (whether by exception or not). attr_reader :teardown_procs # @deprecated # Legacy accessor from when test files would only contain one remote # action. Contains the Result of the last call to utilize # {Beaker::DSL::Helpers#on}. Do not use as it is not safe # in test files that use multiple calls to # {Beaker::DSL::Helpers#on}. attr_accessor :result # @param [Hosts,Array] these_hosts The hosts to execute this test # against/on. # @param [Logger] logger A logger that implements # {Beaker::Logger}'s interface. # @param [Hash{Symbol=>String}] options Parsed command line options. # @param [String] path The local path to a test file to be executed. def initialize(these_hosts, logger, options={}, path=nil) @hosts = these_hosts @logger = logger @sublog = "" @options = options @path = path @usr_home = options[:home] @test_status = :pass @exception = nil @runtime = nil @teardown_procs = [] @metadata = {} @exports = [] set_current_test_filename(@path ? File.basename(@path, '.rb') : nil) # # We put this on each wrapper (rather than the class) so that methods # defined in the tests don't leak out to other tests. class << self def run_test @logger.start_sublog @logger.last_result = nil set_current_step_name(nil) #add arbitrary role methods roles = [] @hosts.each do |host| roles << host[:roles] end add_role_def( roles.flatten.uniq ) @runtime = Benchmark.realtime do begin test = File.read(path) eval test,nil,path,1 rescue FailTest, TEST_EXCEPTION_CLASS => e log_and_fail_test(e, :fail) rescue PassTest @test_status = :pass rescue PendingTest @test_status = :pending rescue SkipTest @test_status = :skip rescue StandardError, ScriptError, SignalException => e log_and_fail_test(e) ensure @logger.info('Begin teardown') @teardown_procs.each do |teardown| begin teardown.call rescue StandardError, SignalException, TEST_EXCEPTION_CLASS => e log_and_fail_test(e, :teardown_error) end end @logger.info('End teardown') end end @sublog = @logger.get_sublog @last_result = @logger.last_result return self end private # Log an error and mark the test as failed, passing through an # exception so it can be displayed at the end of the total run. # # We break out the complete exception backtrace and log each line # individually as well. # # @param exception [Exception] exception to fail with # @param exception [Symbol] the test status def log_and_fail_test(exception, status=:error) logger.error("#{exception.class}: #{exception.message}") bt = exception.backtrace logger.pretty_backtrace(bt).each_line do |line| logger.error(line) end # If the status is already a test failure or error, don't overwrite with the teardown failure. unless status == :teardown_error && (@test_status == :error || @test_status == :fail) status = :error if status == :teardown_error @test_status = status @exception = exception end end end end # The TestCase as a hash # @api public # @note The visibility and semantics of this method are valid, but the # structure of the Hash it returns may change without notice # # @return [Hash] A Hash representation of this test. def to_hash hash = {} hash['HOSTS'] = {} @hosts.each do |host| hash['HOSTS'][host.name] = host.overrides end hash end end end beaker-4.30.0/lib/beaker/test_suite.rb000066400000000000000000000151261407603575700175660ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'fileutils' [ 'test_case', 'logger', 'test_suite_result'].each do |lib| require "beaker/#{lib}" end module Beaker #A collection of {TestCase} objects are considered a {TestSuite}. #Handles executing the set of {TestCase} instances and reporting results as post summary text and JUnit XML. class TestSuite attr_reader :name, :options, :fail_mode #Create {TestSuite} instance #@param [String] name The name of the {TestSuite} #@param [Array] hosts An Array of Hosts to act upon. #@param [Hash{Symbol=>String}] options Options for this object #@option options [Logger] :logger The Logger object to report information to #@option options [String] :log_dir The directory where text run logs will be written #@option options [String] :xml_dir The directory where JUnit XML file will be written #@option options [String] :xml_file The name of the JUnit XML file to be written to #@option options [String] :project_root The full path to the Beaker lib directory #@option options [String] :xml_stylesheet The path to a stylesheet to be applied to the generated XML output #@param [Symbol] fail_mode One of :slow, :fast #@param [Time] timestamp Beaker execution start time def initialize(name, hosts, options, timestamp, fail_mode=nil) @logger = options[:logger] @test_cases = [] @test_files = options[name] @name = name.to_s.gsub(/\s+/, '-') @hosts = hosts @run = false @options = options @fail_mode = fail_mode || @options[:fail_mode] || :slow @test_suite_results = TestSuiteResult.new(@options, name) @timestamp = timestamp report_and_raise(@logger, RuntimeError.new("#{@name}: no test files found..."), "TestSuite: initialize") if @test_files.empty? rescue => e report_and_raise(@logger, e, "TestSuite: initialize") end #Execute all the {TestCase} instances and then report the results as both plain text and xml. The text result #is reported to a newly created run log. #Execution is dependent upon the fail_mode. If mode is :fast then stop running any additional {TestCase} instances #after first failure, if mode is :slow continue execution no matter what {TestCase} results are. def run @run = true start_time = Time.now #Create a run log for this TestSuite. run_log = log_path("#{@name}-run.log", @options[:log_dated_dir]) @logger.add_destination(run_log) # This is an awful hack to maintain backward compatibility until tests # are ported to use logger. Still in use in PuppetDB tests Beaker.const_set(:Log, @logger) unless defined?( Log ) @test_suite_results.start_time = start_time @test_suite_results.total_tests = @test_files.length @test_files.each do |test_file| @logger.info "Begin #{test_file}" start = Time.now test_case = TestCase.new(@hosts, @logger, options, test_file).run_test duration = Time.now - start @test_suite_results.add_test_case(test_case) @test_cases << test_case state = test_case.test_status == :skip ? 'skipp' : test_case.test_status msg = "#{test_file} #{state}ed in %.2f seconds" % duration.to_f case test_case.test_status when :pass @logger.success msg when :skip @logger.warn msg when :fail @logger.error msg break if @fail_mode.to_s !~ /slow/ #all failure modes except slow cause us to kick out early on failure when :error @logger.warn msg break if @fail_mode.to_s !~ /slow/ #all failure modes except slow cause us to kick out early on failure end end @test_suite_results.stop_time = Time.now # REVISIT: This changes global state, breaking logging in any future runs # of the suite – or, at least, making them highly confusing for anyone who # has not studied the implementation in detail. --daniel 2011-03-14 @test_suite_results.summarize( Logger.new(log_path("#{name}-summary.txt", @options[:log_dated_dir]), STDOUT) ) junit_file_log = log_path(@options[:xml_file], @options[:xml_dated_dir]) if @options[:xml_time_enabled] junit_file_time = log_path(@options[:xml_time], @options[:xml_dated_dir]) @test_suite_results.write_junit_xml( junit_file_log, @options[:xml_time] ) @test_suite_results.write_junit_xml( junit_file_time, @options[:xml_file], true ) else @test_suite_results.write_junit_xml( junit_file_log ) end @test_suite_results.persist_test_results(@options[:test_results_file]) #All done with this run, remove run log @logger.remove_destination(run_log) # Allow chaining operations... return self end #Execute all the TestCases in this suite. #This is a wrapper that catches any failures generated during TestSuite::run. def run_and_raise_on_failure begin run return self if @test_suite_results.success? rescue => e #failed during run report_and_raise(@logger, e, "TestSuite :run_and_raise_on_failure") else #failed during test report_and_raise(@logger, RuntimeError.new("Failed while running the #{name} suite"), "TestSuite: report_and_raise_on_failure") end end # Gives a full file path for output to be written to, maintaining the latest symlink # @param [String] name The file name that we want to write to. # @param [String] log_dir The desired output directory. # A symlink will be made from ./basedir/latest to that. # @example # log_path('output.txt', 'log/2014-06-02_16_31_22') # # This will create the structure: # # ./log/2014-06-02_16_31_22/output.txt # ./log/latest -> 2014-06-02_16_31_22 # # @example # log_path('foo.log', 'log/man/date') # # This will create the structure: # # ./log/man/date/foo.log # ./log/latest -> man/date def log_path(name, log_dir) FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir) base_dir = log_dir link_dir = '' while File.dirname(base_dir) != '.' do link_dir = link_dir == '' ? File.basename(base_dir) : File.join(File.basename(base_dir), link_dir) base_dir = File.dirname(base_dir) end latest = File.join(base_dir, "latest") if !File.exist?(latest) or File.symlink?(latest) then File.delete(latest) if File.exist?(latest) || File.symlink?(latest) File.symlink(link_dir, latest) end File.join(log_dir, name) end end end beaker-4.30.0/lib/beaker/test_suite_result.rb000066400000000000000000000232441407603575700211640ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'fileutils' [ 'test_case', 'logger' , 'test_suite', 'logger_junit'].each do |lib| require "beaker/#{lib}" end module Beaker #Holds the output of a test suite, formats in plain text or xml class TestSuiteResult attr_accessor :start_time, :stop_time, :total_tests #Create a {TestSuiteResult} instance. #@param [Hash{Symbol=>String}] options Options for this object #@option options [Logger] :logger The Logger object to report information to #@param [String] name The name of the {TestSuite} that the results are for def initialize( options, name ) @options = options @logger = options[:logger] @name = name @test_cases = [] #Set some defaults, just in case you attempt to print without including them start_time = Time.at(0) stop_time = Time.at(1) end #Add a {TestCase} to this {TestSuiteResult} instance, used in calculating {TestSuiteResult} data. #@param [TestCase] test_case An individual, completed {TestCase} to be included in this set of {TestSuiteResult}. def add_test_case( test_case ) @test_cases << test_case end #How many {TestCase} instances are in this {TestSuiteResult} def test_count @test_cases.length end #How many passed {TestCase} instances are in this {TestSuiteResult} def passed_tests @test_cases.select { |c| c.test_status == :pass }.length end #How many errored {TestCase} instances are in this {TestSuiteResult} def errored_tests @test_cases.select { |c| c.test_status == :error }.length end #How many failed {TestCase} instances are in this {TestSuiteResult} def failed_tests @test_cases.select { |c| c.test_status == :fail }.length end #How many skipped {TestCase} instances are in this {TestSuiteResult} def skipped_tests @test_cases.select { |c| c.test_status == :skip }.length end #How many pending {TestCase} instances are in this {TestSuiteResult} def pending_tests @test_cases.select {|c| c.test_status == :pending}.length end #How many {TestCase} instances failed in this {TestSuiteResult} def sum_failed failed_tests + errored_tests end #Did all the {TestCase} instances in this {TestSuiteResult} pass? def success? sum_failed == 0 end #Did one or more {TestCase} instances in this {TestSuiteResult} fail? def failed? !success? end #The sum of all {TestCase} runtimes in this {TestSuiteResult} def elapsed_time @test_cases.inject(0.0) {|r, t| r + t.runtime.to_f } end #Plain text summay of test suite #@param [Logger] summary_logger The logger we will print the summary to def summarize(summary_logger) summary_logger.notify <<-HEREDOC Test Suite: #{@name} @ #{start_time} - Host Configuration Summary - HEREDOC average_test_time = elapsed_time / test_count summary_logger.notify %Q[ - Test Case Summary for suite '#{@name}' - Total Suite Time: %.2f seconds Average Test Time: %.2f seconds Attempted: #{test_count} Passed: #{passed_tests} Failed: #{failed_tests} Errored: #{errored_tests} Skipped: #{skipped_tests} Pending: #{pending_tests} Total: #{@total_tests} - Specific Test Case Status - ] % [elapsed_time, average_test_time] grouped_summary = @test_cases.group_by{|test_case| test_case.test_status } summary_logger.notify "Failed Tests Cases:" (grouped_summary[:fail] || []).each do |test_case| summary_logger.notify print_test_result(test_case) end summary_logger.notify "Errored Tests Cases:" (grouped_summary[:error] || []).each do |test_case| summary_logger.notify print_test_result(test_case) end summary_logger.notify "Skipped Tests Cases:" (grouped_summary[:skip] || []).each do |test_case| summary_logger.notify print_test_result(test_case) end summary_logger.notify "Pending Tests Cases:" (grouped_summary[:pending] || []).each do |test_case| summary_logger.notify print_test_result(test_case) end summary_logger.notify("\n\n") end #A convenience method for printing the results of a {TestCase} #@param [TestCase] test_case The {TestCase} to examine and print results for def print_test_result(test_case) if test_case.exception test_file_trace = "" test_case.exception.backtrace.each do |line| if line.include?(test_case.path) test_file_trace = "\r\n Test line: #{line}" break end end if test_case.exception.backtrace && test_case.path test_reported = "reported: #{test_case.exception.inspect}#{test_file_trace}" else test_case.test_status end " Test Case #{test_case.path} #{test_reported}" end # Saves failure and error cases as a JSON file for only-failures processing # # @param [String] filepath Where to put the results # def persist_test_results(filepath) return if filepath.empty? results = @test_cases.select { |c| [:fail, :error].include? c.test_status }.map(&:path) File.open(filepath, 'w') { |file| file.puts JSON.dump(results) } end # Writes Junit XML of this {TestSuiteResult} # # @param [String] xml_file Path to the XML file (from Beaker's running directory) # @param [String] file_to_link Path to the paired file that should be linked # from this one (this is relative to the XML # file itself, so it would just be the different # file name if they're in the same directory) # @param [Boolean] time_sort Whether the test results should be output in # order of time spent in the test, or in the # order of test execution (default) # # @return nil # @api private def write_junit_xml(xml_file, file_to_link = nil, time_sort = false) stylesheet = File.join(@options[:project_root], @options[:xml_stylesheet]) begin LoggerJunit.write_xml(xml_file, stylesheet) do |doc, suites| meta_info = suites.add_element(REXML::Element.new('meta_test_info')) unless file_to_link.nil? time_sort ? meta_info.add_attribute('page_active', 'performance') : meta_info.add_attribute('page_active', 'execution') meta_info.add_attribute('link_url', file_to_link) else meta_info.add_attribute('page_active', 'no-links') meta_info.add_attribute('link_url', '') end suite = suites.add_element(REXML::Element.new('testsuite')) suite.add_attributes( [ ['name' , @name], ['tests', test_count], ['errors', errored_tests], ['failures', failed_tests], ['skipped', skipped_tests], ['pending', pending_tests], ['total', @total_tests], ['time', "%f" % (stop_time - start_time)] ]) properties = suite.add_element(REXML::Element.new('properties')) @options.each_pair do |name,value| property = properties.add_element(REXML::Element.new('property')) property.add_attributes([['name', name], ['value', value.to_s || '']]) end test_cases_to_report = @test_cases test_cases_to_report = @test_cases.sort { |x,y| y.runtime <=> x.runtime } if time_sort test_cases_to_report.each do |test| item = suite.add_element(REXML::Element.new('testcase')) item.add_attributes( [ ['classname', File.dirname(test.path)], ['name', File.basename(test.path)], ['time', "%f" % test.runtime] ]) test.exports.each do |export| export.keys.each do |key| item.add_attribute(key.to_s.tr(" ", "_"), export[key]) end end #Report failures if test.test_status == :fail || test.test_status == :error status = item.add_element(REXML::Element.new('failure')) status.add_attribute('type', test.test_status.to_s) if test.exception status.add_attribute('message', test.exception.to_s.gsub(/\e/,'')) data = LoggerJunit.format_cdata(test.exception.backtrace.join('\n')) REXML::CData.new(data, true, status) end end if test.test_status == :skip status = item.add_element(REXML::Element.new('skipped')) status.add_attribute('type', test.test_status.to_s) end if test.test_status == :pending status = item.add_element(REXML::Element.new('pending')) status.add_attribute('type', test.test_status.to_s) end if test.sublog stdout = item.add_element(REXML::Element.new('system-out')) data = LoggerJunit.format_cdata(test.sublog) REXML::CData.new(data, true, stdout) end if test.last_result and test.last_result.stderr and not test.last_result.stderr.empty? stderr = item.add_element('system-err') data = LoggerJunit.format_cdata(test.last_result.stderr) REXML::CData.new(data, true, stderr) end end end rescue Exception => e @logger.error "failure in XML output: \n#{e.to_s}" + e.backtrace.join("\n") end end end end beaker-4.30.0/lib/beaker/version.rb000066400000000000000000000000771407603575700170620ustar00rootroot00000000000000module Beaker module Version STRING = '4.30.0' end end beaker-4.30.0/spec/000077500000000000000000000000001407603575700137775ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/000077500000000000000000000000001407603575700152305ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/cli_spec.rb000066400000000000000000000626271407603575700173530ustar00rootroot00000000000000require 'spec_helper' module Beaker describe CLI do context 'initializing and parsing' do let( :cli ) { Beaker::CLI.new } describe 'instance variable initialization' do it 'creates a logger for use before parse is called' do expect(Beaker::Logger).to receive(:new).once.and_call_original expect(cli.logger).to be_instance_of(Beaker::Logger) end it 'generates the timestamp' do expect(Time).to receive(:now).once cli end end describe '#parse_options' do it 'returns self' do expect(cli.parse_options).to be_instance_of(Beaker::CLI) end it 'replaces the logger object with a new one' do expect(Beaker::Logger).to receive(:new).with(no_args).once.and_call_original expect(Beaker::Logger).to receive(:new).once.and_call_original cli.parse_options end end describe '#parse_options special behavior' do # NOTE: this `describe` block must be separate, with the following `before` block. # Use the above `describe` block for #parse_options when access to the logger object is not needed before do # Within parse_options() the reassignment of cli.logger makes it impossible to capture subsequent logger calls. # So, hijack the reassignment call so that we can keep a reference to it. allow(Beaker::Logger).to receive(:new).with(no_args).once.and_call_original allow(Beaker::Logger).to receive(:new).once.and_return(cli.instance_variable_get(:@logger)) end it 'prints the version and exits cleanly' do expect(cli.logger).to receive(:notify).once expect{ cli.parse_options(['--version']) }.to raise_exception(SystemExit) { |e| expect(e.success?).to eq(true) } end it 'prints the help and exits cleanly' do expect(cli.logger).to receive(:notify).once expect{ cli.parse_options(['--help']) }.to raise_exception(SystemExit) { |e| expect(e.success?).to eq(true) } end end describe '#print_version_and_options' do before do options = Beaker::Options::OptionsHash.new options[:beaker_version] = 'version_number' cli.instance_variable_set('@options', options) end it 'prints the version and dumps the options' do expect(cli.logger).to receive(:info).exactly(3).times cli.print_version_and_options end end end let(:cli) { allow(File).to receive(:exists?).and_return(true) allow(File).to receive(:exists?).with('.beaker.yml').and_return(false) Beaker::CLI.new.parse_options } context '#configured_options' do it 'returns a list of options that were not presets' do attribution = cli.instance_variable_get(:@attribution) attribution.each do |attribute, setter| if setter == 'preset' expect(cli.configured_options[attribute]).to be_nil end end end end describe '#combined_instance_and_options_hosts' do let (:options_host) { {'HOSTS' => {'ubuntu' => {:options_attribute => 'options'}} }} let (:instance_host ) { [Beaker::Host.create('ubuntu', {:platform => 'host'}, {} )] } before do cli.instance_variable_set(:@options, options_host) cli.instance_variable_set(:@hosts, instance_host) end it 'combines the options and instance host objects' do merged_host = cli.combined_instance_and_options_hosts expect(merged_host).to have_key('ubuntu') expect(merged_host['ubuntu']).to have_key(:options_attribute) expect(merged_host['ubuntu']).to have_key(:platform) expect(merged_host['ubuntu'][:options_attribute]).to eq('options') expect(merged_host['ubuntu'][:platform]).to eq('host') end context 'when hosts share IP addresses' do let (:options_host) do {'HOSTS' => {'host1' => {:options_attribute => 'options'}, 'host2' => {:options_attribute => 'options'}}} end let (:instance_host ) do [Beaker::Host.create('host1', {:platform => 'host', :ip => '127.0.0.1'}, {} ), Beaker::Host.create('host2', {:platform => 'host', :ip => '127.0.0.1'}, {} )] end it 'creates separate entries for each host' do expected_hosts = instance_host.map(&:hostname) merged_hosts = cli.combined_instance_and_options_hosts expect(merged_hosts.keys).to eq(expected_hosts) end end end context 'execute!' do before :each do stub_const("Beaker::Logger", double().as_null_object ) File.open("sample.cfg", "w+") do |file| file.write("HOSTS:\n") file.write(" myhost:\n") file.write(" roles:\n") file.write(" - master\n") file.write(" platform: ubuntu-x-x\n") file.write("CONFIG:\n") end allow( cli ).to receive(:setup).and_return(true) allow( cli ).to receive(:validate).and_return(true) allow( cli ).to receive(:provision).and_return(true) end describe "test fail mode" do it 'runs pre_cleanup after a failed pre_suite if using slow fail_mode' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'slow' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]) allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) expect( cli ).to receive(:run_suite).exactly( 2 ).times expect{ cli.execute! }.to raise_error expect(cli.instance_variable_get(:@attribution)[:logger]).to be == 'runtime' expect(cli.instance_variable_get(:@attribution)[:timestamp]).to be == 'runtime' expect(cli.instance_variable_get(:@attribution)[:beaker_version]).to be == 'runtime' end it 'continues testing after failed test if using slow fail_mode' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'slow' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) expect( cli ).to receive(:run_suite).exactly( 4 ).times expect{ cli.execute! }.to raise_error end it 'stops testing after failed test if using fast fail_mode' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) expect( cli ).to receive(:run_suite).exactly( 3 ).times expect{ cli.execute! }.to raise_error end end describe "SUT preserve mode" do it 'cleans up SUTs post testing if tests fail and preserve_hosts = never' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'never' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).once expect{ cli.execute! }.to raise_error end it 'cleans up SUTs post testing if no tests fail and preserve_hosts = never' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'never' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).once expect{ cli.execute! }.to_not raise_error end it 'preserves SUTs post testing if no tests fail and preserve_hosts = always' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'always' options[:log_dated_dir] = '.' options[:hosts_file] = 'sample.cfg' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) cli.instance_variable_set(:@hosts, {}) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).never expect{ cli.execute! }.to_not raise_error end it 'preserves SUTs post testing if no tests fail and preserve_hosts = always' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'always' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).never expect{ cli.execute! }.to raise_error end it 'cleans up SUTs post testing if no tests fail and preserve_hosts = onfail' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onfail' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).once expect{ cli.execute! }.to_not raise_error end it 'preserves SUTs post testing if tests fail and preserve_hosts = onfail' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onfail' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).never expect{ cli.execute! }.to raise_error end it 'cleans up SUTs post testing if tests fail and preserve_hosts = onpass' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onpass' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_throw("bad test") allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).once expect{ cli.execute! }.to raise_error end it 'preserves SUTs post testing if no tests fail and preserve_hosts = onpass' do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onpass' options[:log_dated_dir] = '.' options[:hosts_file] = 'sample.cfg' cli.instance_variable_set(:@hosts, {}) cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).never expect{ cli.execute! }.to_not raise_error end end describe "#preserve_hosts_file" do it 'removes the pre-suite/post-suite/tests and sets to []' do hosts = make_hosts options = cli.instance_variable_get(:@options) options[:log_dated_dir] = Dir.mktmpdir File.open("sample.cfg", "w+") do |file| file.write("HOSTS:\n") hosts.each do |host| file.write(" #{host.name}:\n") file.write(" roles:\n") host[:roles].each do |role| file.write(" - #{role}\n") end file.write(" platform: #{host[:platform]}\n") end file.write("CONFIG:\n") end options[:hosts_file] = 'sample.cfg' options[:pre_suite] = ['pre1', 'pre2', 'pre3'] options[:post_suite] = ['post1'] options[:pre_cleanup] = ['preclean1'] options[:tests] = ['test1', 'test2'] cli.instance_variable_set(:@options, options) cli.instance_variable_set(:@hosts, hosts) preserved_file = cli.preserve_hosts_file hosts_yaml = YAML.load_file(preserved_file) expect(hosts_yaml['CONFIG'][:tests]).to be == [] expect(hosts_yaml['CONFIG'][:pre_suite]).to be == [] expect(hosts_yaml['CONFIG'][:post_suite]).to be == [] expect(hosts_yaml['CONFIG'][:pre_cleanup]).to be == [] end end describe 'hosts file saving when preserve_hosts should happen' do before :each do options = cli.instance_variable_get(:@options) options[:fail_mode] = 'fast' options[:preserve_hosts] = 'onpass' options[:hosts_file] = 'sample.cfg' cli.instance_variable_set(:@options, options) allow( cli ).to receive(:run_suite).with(:pre_suite, :fast).and_return(true) allow( cli ).to receive(:run_suite).with(:tests, options[:fail_mode]).and_return(true) allow( cli ).to receive(:run_suite).with(:post_suite).and_return(true) allow( cli ).to receive(:run_suite).with(:pre_cleanup).and_return(true) hosts = [ make_host('petey', { :hypervisor => 'peterPan' }), make_host('hatty', { :hypervisor => 'theMadHatter' }), ] cli.instance_variable_set(:@hosts, hosts) netmanager = double(:netmanager) cli.instance_variable_set(:@network_manager, netmanager) expect( netmanager ).to receive(:cleanup).never allow( cli ).to receive( :print_env_vars_affecting_beaker ) logger = cli.instance_variable_get(:@logger) expect( logger ).to receive( :send ).with( anything, anything ).ordered expect( logger ).to receive( :send ).with( anything, anything ).ordered end it 'executes without error' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) expect{ cli.execute! }.to_not raise_error end end it 'copies a file into the correct location' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) cli.execute! copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') expect( File.exists?(copied_hosts_file) ).to be_truthy end end it 'generates a valid YAML file when it copies' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) cli.execute! copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') expect{ YAML.load_file(copied_hosts_file) }.to_not raise_error end end it 'sets :provision to false in the copied hosts file' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) cli.execute! copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') yaml_content = YAML.load_file(copied_hosts_file) expect( yaml_content['CONFIG']['provision'] ).to be_falsy end end it 'sets the @options :hosts_preserved_yaml_file to the copied file' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) expect( options.has_key?(:hosts_preserved_yaml_file) ).to be_falsy cli.execute! expect( options.has_key?(:hosts_preserved_yaml_file) ).to be_truthy copied_hosts_file = File.join(File.absolute_path(dir), 'hosts_preserved.yml') expect( options[:hosts_preserved_yaml_file] ).to be === copied_hosts_file end end describe 'output text informing the user that re-use is possible' do it 'if unsupported, does not output extra text' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) copied_hosts_file = File.join(File.absolute_path(dir), options[:hosts_file]) logger = cli.instance_variable_get(:@logger) expect( logger ).to receive( :send ).with( anything, "\nYou can re-run commands against the already provisioned SUT(s) by following these steps:\n").never expect( logger ).to receive( :send ).with( anything, "- change the hosts file to #{copied_hosts_file}").never expect( logger ).to receive( :send ).with( anything, '- use the --no-provision flag').never cli.execute! end end it 'if supported, outputs the text letting the user know they can re-use these hosts' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) copied_hosts_file = File.join(File.absolute_path(dir), options[:hosts_file]) hosts = cli.instance_variable_get(:@hosts) hosts << make_host('fusion', { :hypervisor => 'fusion' }) reproducing_cmd = "the faith of the people" allow( cli ).to receive( :build_hosts_preserved_reproducing_command ).and_return( reproducing_cmd ) logger = cli.instance_variable_get(:@logger) expect( logger ).to receive( :send ).with( anything, "\nYou can re-run commands against the already provisioned SUT(s) with:\n").ordered expect( logger ).to receive( :send ).with( anything, reproducing_cmd).ordered cli.execute! end end it 'if supported && docker is a hypervisor, outputs text + the untested warning' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) copied_hosts_file = File.join(File.absolute_path(dir), options[:hosts_file]) hosts = cli.instance_variable_get(:@hosts) hosts << make_host('fusion', { :hypervisor => 'fusion' }) hosts << make_host('docker', { :hypervisor => 'docker' }) reproducing_cmd = "the crow flies true says the shoe to you" allow( cli ).to receive( :build_hosts_preserved_reproducing_command ).and_return( reproducing_cmd ) logger = cli.instance_variable_get(:@logger) expect( logger ).to receive( :send ).with( anything, "\nYou can re-run commands against the already provisioned SUT(s) with:\n").ordered expect( logger ).to receive( :send ).with( anything, '(docker support is untested for this feature. please reference the docs for more info)').ordered expect( logger ).to receive( :send ).with( anything, reproducing_cmd).ordered cli.execute! end end it 'if unsupported && docker is a hypervisor, no extra text output' do options = cli.instance_variable_get(:@options) Dir.mktmpdir do |dir| options[:log_dated_dir] = File.absolute_path(dir) copied_hosts_file = File.join(File.absolute_path(dir), options[:hosts_file]) hosts = cli.instance_variable_get(:@hosts) hosts << make_host('docker', { :hypervisor => 'docker' }) logger = cli.instance_variable_get(:@logger) expect( logger ).to receive( :send ).with( anything, "\nYou can re-run commands against the already provisioned SUT(s) with:\n").never expect( logger ).to receive( :send ).with( anything, '(docker support is untested for this feature. please reference the docs for more info)').never expect( logger ).to receive( :send ).with( anything, "- change the hosts file to #{copied_hosts_file}").never expect( logger ).to receive( :send ).with( anything, '- use the --no-provision flag').never cli.execute! end end end end describe '#build_hosts_preserved_reproducing_command' do it 'replaces the hosts file' do new_hosts_file = 'john/deer/was/here.txt' command_to_sub = 'p --log-level debug --hosts pants/of/plan.poo jam --jankies --flag-business' command_correct = "p --log-level debug --hosts #{new_hosts_file} jam --jankies --flag-business" answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, new_hosts_file) expect( answer.start_with?(command_correct) ).to be_truthy end it 'doesn\'t replace an entry if no --hosts key is found' do command_to_sub = 'p --log-level debug johnnypantaloons7 --jankies --flag-business' command_correct = 'p --log-level debug johnnypantaloons7 --jankies --flag-business' answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, 'john/deer/plans.txt') expect( answer.start_with?(command_correct) ).to be_truthy end it 'removes any old --provision flags' do command_to_sub = '--provision jam --provision --jankies --flag-business' command_correct = 'jam --jankies --flag-business' answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, 'can/talk/to/pigs.yml') expect( answer.start_with?(command_correct) ).to be_truthy end it 'removes any old --no-provision flags' do command_to_sub = 'jam --no-provision --jankoos --no-provision --flag-businesses' command_correct = 'jam --jankoos --flag-businesses' answer = cli.build_hosts_preserved_reproducing_command(command_to_sub, 'can/talk/to/bears.yml') expect( answer.start_with?(command_correct) ).to be_truthy end end end end end beaker-4.30.0/spec/beaker/command_spec.rb000066400000000000000000000126401407603575700202100ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Command do let(:command) { @command || '/bin/ls' } let(:args) { @args || Array.new } let(:options) { @options || Hash.new } subject(:cmd) { Command.new( command, args, options ) } let(:host) { h = Hash.new allow( h ).to receive( :environment_string ).and_return( '' ) h } it 'creates a new Command object' do @command = '/usr/bin/blah' @args = [ 'to', 'the', 'baz' ] @options = { :foo => 'bar' } expect( cmd.options ).to be == @options expect( cmd.args ).to be == @args expect( cmd.command ).to be == @command expect( cmd.args_string ).to be == 'to the baz' expect( cmd.options_string ).to be == '--foo=bar' end describe '#:prepend_cmds' do it 'can prepend commands' do @command = '/usr/bin/blah' @args = [ 'to', 'the', 'baz' ] @options = { :foo => 'bar' } allow( host ).to receive( :prepend_commands ).and_return( 'aloha!' ) allow( host ).to receive( :append_commands ).and_return( '' ) expect( cmd.cmd_line( host ) ).to be == "aloha! /usr/bin/blah --foo=bar to the baz" end it 'can handle no prepend_cmds' do @command = '/usr/bin/blah' @args = [ 'to', 'the', 'baz' ] @options = { :foo => 'bar' } allow( host ).to receive( :prepend_commands ).and_return( '' ) allow( host ).to receive( :append_commands ).and_return( '' ) expect( cmd.cmd_line( host ) ).to be == "/usr/bin/blah --foo=bar to the baz" end end describe '#:append_commands' do it 'can append commands' do @command = '/usr/bin/blah' @args = [ 'to', 'the', 'baz' ] @options = { :foo => 'bar' } allow( host ).to receive( :prepend_commands ).and_return( 'aloha!' ) allow( host ).to receive( :append_commands ).and_return( 'moo cow' ) expect( cmd.cmd_line( host ) ).to be == "aloha! /usr/bin/blah --foo=bar to the baz moo cow" end it 'can handle no append_cmds' do @command = '/usr/bin/blah' @args = [ 'to', 'the', 'baz' ] @options = { :foo => 'bar' } allow( host ).to receive( :prepend_commands ).and_return( '' ) allow( host ).to receive( :append_commands ).and_return( '' ) expect( cmd.cmd_line( host ) ).to be == "/usr/bin/blah --foo=bar to the baz" end end describe '#options_string' do it 'parses things' do subject.options = { :v => nil, :test => nil, :server => 'master', :a => 'answers.txt' } expect( subject.options_string ).to match /-v/ expect( subject.options_string ).to match /--test/ expect( subject.options_string ).to match /--server=master/ expect( subject.options_string ).to match /-a=answers\.txt/ end end describe '#args_string' do it 'joins an array' do subject.args = ['my/command and', nil, 'its args and opts'] expect( subject.args_string ).to be == 'my/command and its args and opts' end end end describe HostCommand do let(:command) { @command || '/bin/ls' } let(:args) { @args || Array.new } let(:options) { @options || Hash.new } subject(:cmd) { HostCommand.new( command, args, options ) } let(:host) { Hash.new } it 'returns a simple string passed in' do @command = "pants" expect( cmd.cmd_line host ).to be === @command end it 'returns single quoted string correctly' do @command = "str_p = 'pants'; str_p" expect( cmd.cmd_line host ).to be === @command end it 'returns empty strings when given the escaped version of the same' do @command = "\"\"" expect( cmd.cmd_line host ).to be === "" end end describe SedCommand do let(:host) { h = Hash.new allow( h ).to receive( :environment_string ).and_return( '' ) allow( h ).to receive( :prepend_commands ).and_return( '' ) allow( h ).to receive( :append_commands ).and_return( '' ) h } let(:platform) { @platform || 'unix' } let(:expression) { @expression || 's/b/s/' } let(:filename) { @filename || '/fakefile' } let(:options) { @options || Hash.new } subject(:cmd) { SedCommand.new( platform, expression, filename, options ) } it 'forms a basic sed command correctly' do expect( cmd.cmd_line host ).to be === "sed -i -e \"#{expression}\" #{filename}" end it 'provides the -i option to rewrite file in-place on non-solaris hosts' do expect( cmd.cmd_line host ).to include('-i') end describe 'on solaris hosts' do it 'removes the -i option correctly' do @platform = 'solaris' expect( cmd.cmd_line host ).not_to include('-i') end it 'deals with in-place file substitution correctly' do @platform = 'solaris' default_temp_file = "#{filename}.tmp" expect( cmd.cmd_line host ).to include(" > #{default_temp_file} && mv #{default_temp_file} #{filename} && rm -f #{default_temp_file}") end it 'allows you to provide the name of the temp file for in-place file substitution' do @platform = 'solaris' temp_file = 'mytemp.tmp' @options = { :temp_file => temp_file } expect( cmd.cmd_line host ).to include(" > #{temp_file} && mv #{temp_file} #{filename} && rm -f #{temp_file}") end end end end beaker-4.30.0/spec/beaker/dsl/000077500000000000000000000000001407603575700160125ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/dsl/assertions_spec.rb000066400000000000000000000054271407603575700215530ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLAssertions include Beaker::DSL::Assertions end describe ClassMixedWithDSLAssertions do describe '#assert_output' do it 'defaults to checking stdout' do stdout = < This is on stdout STDERR> While this is on stderr STDOUT> And THIS is again on stdout EXPECT result = double expect( result ).to receive( :nil? ).at_least( :once ).and_return( false ) expect( result ).to receive( :stdout ).and_return( stdout ) expect( result ).to receive( :output ).and_return( output ) expect( result ).to receive( :stderr ).and_return( stderr ) expect( subject ).to receive( :result ).at_least( :once ).and_return( result ) expect { subject.assert_output expectation }.to_not raise_error end it 'raises an approriate error when output does not match expectations' do FakeFS.without do output = < This is on stdout STDERR> While this is on stderr STDOUT> And THIS is again on stdout EXPECT result = double expect( result ).to receive( :nil? ).at_least( :once ).and_return( false ) expect( result ).to receive( :output ).and_return( output ) expect( subject ).to receive( :result ).at_least( :once ).and_return( result ) expect { subject.assert_output expectation }.to raise_error( MiniTest::Assertion ) end end end end beaker-4.30.0/spec/beaker/dsl/helpers/000077500000000000000000000000001407603575700174545ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/dsl/helpers/host_helpers_spec.rb000066400000000000000000000275051407603575700235230ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLHelpers include Beaker::DSL::Helpers include Beaker::DSL::Wrappers include Beaker::DSL::Roles include Beaker::DSL::Patterns def logger RSpec::Mocks::Double.new('logger').as_null_object end end describe ClassMixedWithDSLHelpers do let( :opts ) { Beaker::Options::Presets.env_vars } let( :command ){ 'ls' } let( :host ) { double.as_null_object } let( :result ) { Beaker::Result.new( host, command ) } let( :master ) { make_host( 'master', :roles => %w( master agent default) ) } let( :agent ) { make_host( 'agent', :roles => %w( agent ) ) } let( :custom ) { make_host( 'custom', :roles => %w( custom agent ) ) } let( :dash ) { make_host( 'console', :roles => %w( dashboard agent ) ) } let( :db ) { make_host( 'db', :roles => %w( database agent ) ) } let( :hosts ) { [ master, agent, dash, db, custom ] } describe '#on' do before :each do result.stdout = 'stdout' result.stderr = 'stderr' result.exit_code = 0 end it 'allows the environment the command is run within to be specified' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( Beaker::Command ).to receive( :new ). with( 'ls ~/.bin', [], {'ENV' => { :HOME => '/tmp/test_home' }} ) subject.on( host, 'ls ~/.bin', :environment => {:HOME => '/tmp/test_home' } ) end describe 'with a beaker command object passed in as the command argument' do let( :command ) { Beaker::Command.new('commander command', [], :environment => {:HOME => 'default'}) } it 'overwrites the command environment with the environment specified in #on' do expect( host ).to receive( :exec ) do |command| expect(command.environment).to eq({:HOME => 'override'}) end subject.on( host, command, :environment => {:HOME => 'override'}) end it 'uses the command environment if there is no overriding argument in #on' do expect( host ).to receive( :exec ) do |command| expect(command.environment).to eq({:HOME => 'default'}) end subject.on( host, command ) end end it 'if the host is a String Object, finds the matching hosts with that String as role' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( master ).to receive( :exec ).once subject.on( 'master', 'echo hello') end it 'if the host is a Symbol Object, finds the matching hosts with that Symbol as role' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( master ).to receive( :exec ).once subject.on( :master, 'echo hello') end it 'executes in parallel if run_in_parallel=true' do InParallel::InParallelExecutor.logger = logger FakeFS.deactivate! allow( subject ).to receive( :hosts ).and_return( hosts ) expected = [] hosts.each_with_index do |host, i| expected << i allow( host ).to receive( :exec ).and_return( i ) end # This will only get hit if forking processes is supported and at least 2 items are being submitted to run in parallel expect( InParallel::InParallelExecutor ).to receive(:_execute_in_parallel).with(any_args).and_call_original.exactly(5).times results = subject.on( hosts, command, {:run_in_parallel => true}) expect( results ).to be == expected end it 'delegates to itself for each host passed' do allow( subject ).to receive( :hosts ).and_return( hosts ) expected = [] hosts.each_with_index do |host, i| expected << i expect( host ).to receive( :exec ).and_return( i ) end results = subject.on( hosts, command ) expect( results ).to be == expected end context 'upon command completion' do before :each do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( host ).to receive( :exec ).and_return( result ) @res = subject.on( host, command ) end it 'returns the result of the action' do expect( @res ).to be == result end it 'provides access to stdout' do expect( @res.stdout ).to be == 'stdout' end it 'provides access to stderr' do expect( @res.stderr ).to be == 'stderr' end it 'provides access to exit_code' do expect( @res.exit_code ).to be == 0 end end context 'when passed a block with arity of 1' do before :each do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( host ).to receive( :exec ).and_return( result ) end it 'yields result' do subject.on host, command do |containing_class| expect( containing_class ). to be_an_instance_of( Beaker::Result ) end end it 'provides access to stdout' do subject.on host, command do |containing_class| expect( containing_class.stdout ).to be == 'stdout' end end it 'provides access to stderr' do subject.on host, command do |containing_class| expect( containing_class.stderr ).to be == 'stderr' end end it 'provides access to exit_code' do subject.on host, command do |containing_class| expect( containing_class.exit_code ).to be == 0 end end end context 'when passed a block with arity of 0' do before :each do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( host ).to receive( :exec ).and_return( result ) end it 'yields self' do subject.on host, command do expect( subject ). to be_an_instance_of( ClassMixedWithDSLHelpers ) end end it 'provides access to stdout' do subject.on host, command do expect( subject.stdout ).to be == 'stdout' end end it 'provides access to stderr' do subject.on host, command do expect( subject.stderr ).to be == 'stderr' end end it 'provides access to exit_code' do subject.on host, command do expect( subject.exit_code ).to be == 0 end end end it 'errors if command is not a String or Beaker::Command' do expect { subject.on( host, Object.new ) }.to raise_error( ArgumentError, /called\ with\ a\ String\ or\ Beaker/ ) end it 'executes the passed Beaker::Command if given as command argument' do command_test = Beaker::Command.new( 'echo face_testing' ) expect( master ).to receive( :exec ).with( command_test, anything ) subject.on( master, command_test ) end end describe "#retry_on" do it 'fails correctly when command never succeeds' do result.stdout = 'stdout' result.stderr = 'stderr' result.exit_code = 1 retries = 5 opts = { :max_retries => retries, :retry_interval => 0.0001, } allow( subject ).to receive(:on).and_return(result) expect( subject ).to receive(:on).exactly(retries+2) expect { subject.retry_on(host, command, opts) }.to raise_error(RuntimeError) end it 'will return success correctly if it succeeds the first time' do result.stdout = 'stdout' result.stderr = 'stderr' result.exit_code = 0 opts = { :max_retries => 5, :retry_interval => 0.0001, } allow( subject ).to receive(:on).and_return(result) expect( subject ).to receive(:on).once result_given = subject.retry_on(host, command, opts) expect(result_given.exit_code).to be === 0 end it 'will return success correctly if it succeeds after failing a few times' do result.stdout = 'stdout' result.stderr = 'stderr' opts = { :max_retries => 10, :retry_interval => 0.1, } reps_num = 4 count = 0 allow( subject ).to receive(:on) do result.exit_code = count > reps_num ? 0 : 1 count += 1 result end expect( subject ).to receive(:on).exactly(reps_num + 2) result_given = subject.retry_on(host, command, opts) expect(result_given.exit_code).to be === 0 end end describe "shell" do it 'delegates to #on with the default host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :on ).with( master, "echo hello", {}).once subject.shell( "echo hello" ) end end describe '#scp_from' do it 'delegates to the host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :logger ).exactly( hosts.length ).times expect( result ).to receive( :log ).exactly( hosts.length ).times hosts.each do |host| expect( host ).to receive( :do_scp_from ).and_return( result ) end subject.scp_from( hosts, '/var/log/my.log', 'log/my.log' ) end end describe '#scp_to' do it 'delegates to the host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :logger ).exactly( hosts.length ).times expect( result ).to receive( :log ).exactly( hosts.length ).times hosts.each do |host| expect( host ).to receive( :do_scp_to ).and_return( result ) end subject.scp_to( hosts, '/var/log/my.log', 'log/my.log' ) end end describe '#rsync_to' do it 'delegates to the host' do allow( subject ).to receive( :hosts ).and_return( hosts ) hosts.each do |host| expect( host ).to receive( :do_rsync_to ).and_return( result ) end subject.rsync_to( hosts, '/var/log/my.log', 'log/my.log' ) end end describe '#create_remote_file using scp' do it 'scps the contents passed in to the hosts' do my_opts = { :silent => true } tmpfile = double expect( tmpfile ).to receive( :path ).exactly( 2 ).times. and_return( '/local/path/to/blah' ) expect( Tempfile ).to receive( :open ).and_yield( tmpfile ) expect( File ).to receive( :open ) expect( subject ).to receive( :scp_to ). with( hosts, '/local/path/to/blah', '/remote/path', my_opts ) subject.create_remote_file( hosts, '/remote/path', 'blah', my_opts ) end end describe '#create_remote_file using rsync' do it 'scps the contents passed in to the hosts' do my_opts = { :silent => true, :protocol => 'rsync' } tmpfile = double expect( tmpfile ).to receive( :path ).exactly( 2 ).times. and_return( '/local/path/to/blah' ) expect( Tempfile ).to receive( :open ).and_yield( tmpfile ) expect( File ).to receive( :open ) expect( subject ).to receive( :rsync_to ). with( hosts, '/local/path/to/blah', '/remote/path', my_opts ) subject.create_remote_file( hosts, '/remote/path', 'blah', my_opts ) end end describe '#run_script_on' do it 'scps the script to a tmpdir and executes it on host(s)' do expect( subject ).to receive( :scp_to ) expect( subject ).to receive( :on ) subject.run_script_on( 'host', '~/.bin/make-enterprisy' ) end end describe '#run_script' do it 'delegates to #run_script_on with the default host' do allow( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :run_script_on ).with( master, "/tmp/test.sh", {}).once subject.run_script( '/tmp/test.sh' ) end end describe '#install_package' do it 'delegates to Host#install_package with arguments on the passed Host' do expect( host ).to receive( :install_package ).with( 'pkg_name', '', '1.2.3' ) subject.install_package( host, 'pkg_name', '1.2.3' ) end end describe '#uninstall_package' do it 'delegates to Host#uninstall_package on the passed Host' do expect( host ).to receive( :uninstall_package ).with( 'pkg_name' ) subject.uninstall_package( host, 'pkg_name' ) end end end beaker-4.30.0/spec/beaker/dsl/helpers/test_helpers_spec.rb000066400000000000000000000045161407603575700235220ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLHelpers include Beaker::DSL::Helpers include Beaker::DSL::Wrappers include Beaker::DSL::Roles include Beaker::DSL::Patterns def logger RSpec::Mocks::Double.new('logger').as_null_object end end describe ClassMixedWithDSLHelpers do let(:metadata) { @metadata ? @metadata : {} } describe '#current_test_name' do it 'returns nil if the case is undefined' do allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_test_name ).to be_nil end it 'returns nil if the name is undefined' do @metadata = { :case => {} } allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_test_name ).to be_nil end it 'returns the set value' do name = 'holyGrail_testName' @metadata = { :case => { :name => name } } allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_test_name ).to be === name end end describe '#current_test_filename' do it 'returns nil if the case is undefined' do allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_test_filename ).to be_nil end it 'returns nil if the name is undefined' do @metadata = { :case => {} } allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_test_filename ).to be_nil end it 'returns the set value' do name = 'holyGrail_testFilename' @metadata = { :case => { :file_name => name } } allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_test_filename ).to be === name end end describe '#current_step_name' do it 'returns nil if the step is undefined' do allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_step_name ).to be_nil end it 'returns nil if the name is undefined' do @metadata = { :step => {} } allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_step_name ).to be_nil end it 'returns the set value' do name = 'holyGrail_stepName' @metadata = { :step => { :name => name } } allow(subject).to receive(:metadata).and_return(metadata) expect( subject.current_step_name ).to be === name end end end beaker-4.30.0/spec/beaker/dsl/helpers/web_helpers_spec.rb000066400000000000000000000063511407603575700233170ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLHelpers include Beaker::DSL::Helpers include Beaker::DSL::Wrappers include Beaker::DSL::Roles include Beaker::DSL::Patterns def logger RSpec::Mocks::Double.new('logger').as_null_object end end describe ClassMixedWithDSLHelpers do let( :logger ) { double("Beaker::Logger", :notify => nil , :debug => nil ) } let( :url ) { "http://beaker.tool" } let( :name ) { "name" } let( :destdir ) { "destdir" } def fetch_allows allow( subject ).to receive( :logger ) { logger } allow( subject ).to receive( :options ) { options } end describe "#fetch_http_file" do let( :presets ) { Beaker::Options::Presets.new } let( :options ) { presets.presets.merge(presets.env_vars) } before do fetch_allows end describe "given valid arguments" do it "returns its second and third arguments concatenated." do concat_path = "#{destdir}/#{name}" create_files([concat_path]) allow(logger).to receive(:notify) allow(subject).to receive(:open) result = subject.fetch_http_file url, name, destdir expect(result).to eq(concat_path) end it 'doesn\'t cache by default' do expect( logger ).to receive( :notify ).with( /^Fetching/ ).ordered expect( logger ).to receive( :notify ).with( /^\ \ and\ saving\ to\ / ).ordered expect( subject ).to receive( :open ) subject.fetch_http_file( url, name, destdir ) end context ':cache_files_locally option is set' do it 'caches if the file exists locally' do options[:cache_files_locally] = true allow(File).to receive(:exists?).and_return(true) expect( logger ).to receive( :notify ).with( /^Already\ fetched\ / ) expect( subject ).not_to receive( :open ) subject.fetch_http_file( url, name, destdir ) end it 'doesn\'t cache if the file doesn\'t exist locally' do options[:cache_files_locally] = true allow(File).to receive(:exists?).and_return(false) expect( logger ).to receive( :notify ).with( /^Fetching/ ).ordered expect( logger ).to receive( :notify ).with( /^\ \ and\ saving\ to\ / ).ordered expect( subject ).to receive( :open ) subject.fetch_http_file( url, name, destdir ) end end end describe 'given invalid arguments' do it 'chomps correctly when given a URL ending with a / character' do expect( subject ).to receive( :open ).with( "#{url}/#{name}", anything ) subject.fetch_http_file( url, name, destdir ) end end end describe "#fetch_http_dir" do let( :logger) { double("Beaker::Logger", :notify => nil , :debug => nil ) } let( :result) { double(:each_line => []) } let( :status) { double('Process::Status', success?: true) } before do fetch_allows end describe "given valid arguments" do it "returns basename of first argument concatenated to second." do expect(Open3).to receive(:capture2e).with(/^wget.*/).ordered { result }.and_return(['', status]) result = subject.fetch_http_dir "#{url}/beep", destdir expect(result).to eq("#{destdir}/beep") end end end end beaker-4.30.0/spec/beaker/dsl/outcomes_spec.rb000066400000000000000000000025111407603575700212060ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLOutcomes include Beaker::DSL::Outcomes end describe ClassMixedWithDSLOutcomes do let(:logger) { double } before { allow( subject ).to receive( :logger ).and_return( logger ) } describe '#pass_test' do it "logs the notification passed to it and raises PassTest" do expect( logger ).to receive( :notify ).with( /blah/ ) expect { subject.pass_test('blah') }. to raise_error Beaker::DSL::Outcomes::PassTest end end describe '#skip_test' do it "logs the notification passed to it and raises SkipTest" do expect( logger ).to receive( :notify ).with( /blah/ ) expect { subject.skip_test('blah') }. to raise_error Beaker::DSL::Outcomes::SkipTest end end describe '#pending_test' do it "logs the notification passed to it and raises PendingTest" do expect( logger ).to receive( :warn ).with( /blah/ ) expect { subject.pending_test('blah') }. to raise_error Beaker::DSL::Outcomes::PendingTest end end describe '#fail_test' do it "logs the notification passed to it and raises FailTest" do expect( logger ).to receive( :warn ) expect( logger ).to receive( :pretty_backtrace ) expect { subject.fail_test('blah') }. to raise_error Beaker::DSL::Outcomes::FailTest end end end beaker-4.30.0/spec/beaker/dsl/roles_spec.rb000066400000000000000000000333231407603575700205010ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLRoles include Beaker::DSL::Roles include Beaker::DSL::Outcomes end describe ClassMixedWithDSLRoles do let( :hosts ) { @hosts || Hash.new } let( :options ) { @options || Hash.new } let( :agent1 ) { make_host( 'agent1', { :roles => [ 'agent' ] } ) } let( :agent2 ) { make_host( 'agent2', { :roles => [ 'agent' ] } ) } let( :a_and_dash ) { make_host( 'a_and_dash', { :roles => [ 'agent', 'dashboard' ] } ) } let( :custom ) { make_host( 'custom', { :roles => [ 'custom_role' ] } ) } let( :db ) { make_host( 'db', { :roles => [ 'database' ] } ) } let( :master ) { make_host( 'master', { :roles => [ 'master', 'agent' ] } ) } let( :default ) { make_host( 'default', { :roles => [ 'default'] } ) } let( :monolith ) { make_host( 'monolith', { :roles => [ 'agent', 'dashboard', 'database', 'master', 'custom_role'] } ) } describe '#agents' do it 'returns an array of hosts that are agents' do @hosts = [ agent1, agent2, master ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject.agents ).to be == [ agent1, agent2, master ] end it 'and an empty array when none match' do @hosts = [ db, custom ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject.agents ).to be == [] end end describe '#master' do it 'returns the master if there is one' do @hosts = [ master, agent1 ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject.master ).to be == master end it 'raises an error if there is more than one master' do @hosts = [ master, monolith ] expect( subject ).to receive( :hosts ).exactly( 1 ).times.and_return( hosts ) expect { subject.master }.to raise_error Beaker::DSL::FailTest end it 'returns nil if no master and masterless is set' do @options = { :masterless => true } expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :options ).and_return( options ) expect( subject.master ).to be_nil end end describe '#dashboard' do it 'returns the dashboard if there is one' do @hosts = [ a_and_dash, agent1 ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject.dashboard ).to be == a_and_dash end it 'raises an error if there is more than one dashboard' do @hosts = [ a_and_dash, monolith ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect { subject.dashboard }.to raise_error Beaker::DSL::FailTest end it 'and raises an error if there is no dashboard' do @hosts = [ agent1, agent2, custom ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect { subject.dashboard }.to raise_error Beaker::DSL::FailTest end it 'returns nil if no dashboard and masterless is set' do @options = { :masterless => true } expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :options ).and_return( options ) expect( subject.dashboard ).to be_nil end end describe '#database' do it 'returns the database if there is one' do @hosts = [ db, agent1 ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject.database ).to be == db end it 'raises an error if there is more than one database' do @hosts = [ db, monolith ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect { subject.database }.to raise_error Beaker::DSL::FailTest end it 'and raises an error if there is no database' do @hosts = [ agent1, agent2, custom ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect { subject.database }.to raise_error Beaker::DSL::FailTest end it 'returns nil if no database and masterless is set' do @options = { :masterless => true } expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :options ).and_return( options ) expect( subject.database ).to be_nil end end describe '#not_controller' do it 'returns true when a host does not have the roles master/database/dashboard' do expect( subject.not_controller(agent1) ).to be == true end it 'returns false when a host has one of the roles master/database/dashboard' do expect( subject.not_controller(a_and_dash) ).to be == false end end describe '#agent_only' do it 'returns true when a host has the single role agent' do expect( subject.agent_only(agent1) ).to be == true end it 'returns false when a host has more than a single role' do expect( subject.agent_only(a_and_dash) ).to be == false end it 'returns false when a host has the role master' do expect( subject.agent_only(master) ).to be == false end end describe '#aio_version?' do it 'returns false if the host doesn\'t have a :pe_ver or :version' do agent1[:pe_ver] = nil agent1[:version] = nil expect( subject.aio_version?(agent1) ).to be === false end it 'returns false if :version < 4.0 and pe_ver is nil, type foss' do agent1[:pe_ver] = nil agent1[:version] = '3.8' agent1[:type] = 'foss' expect( subject.aio_version?(agent1) ).to be === false end it 'returns false if the host :pe_ver is set < 4.0' do agent1[:pe_ver] = '3.8' expect( subject.aio_version?(agent1) ).to be === false end it 'returns false if the host :version is set < 4.0' do agent1[:version] = '3.8' expect( subject.aio_version?(agent1) ).to be === false end it 'returns true if the host :pe_ver is 4.0' do agent1[:pe_ver] = '4.0' expect( subject.aio_version?(agent1) ).to be === true end it 'returns true if the host :version is 4.0' do agent1[:version] = '4.0' expect( subject.aio_version?(agent1) ).to be === true end it 'returns true if the host :pe_ver is 2015.5' do agent1[:pe_ver] = '2015.5' expect( subject.aio_version?(agent1) ).to be === true end it 'returns true if the host has role aio' do agent1[:roles] = agent1[:roles] | ['aio'] expect( subject.aio_version?(agent1) ).to be === true end it 'returns true if the host is type aio' do agent1[:type] = 'aio' expect( subject.aio_version?(agent1) ).to be === true end it 'returns true if the host is type aio-foss' do agent1[:type] = 'aio-foss' expect( subject.aio_version?(agent1) ).to be === true end it 'returns true if the host is type foss-aio' do agent1[:type] = 'aio-foss' expect( subject.aio_version?(agent1) ).to be === true end it 'can take an empty string for pe_ver' do agent1[:pe_ver] = '' expect{ subject.aio_version?(agent1) }.not_to raise_error end it 'can take an empty string for FOSS version' do agent1[:version] = '' expect{ subject.aio_version?(agent1) }.not_to raise_error end context 'truth table-type testing' do before :each do @old_pe_ver = agent1[:pe_ver] @old_version = agent1[:version] @old_roles = agent1[:roles] @old_type = agent1[:type] end after :each do agent1[:pe_ver] = @old_pe_ver agent1[:version] = @old_version agent1[:roles] = @old_roles agent1[:type] = @old_type end context 'version values table' do # pe_ver, version, answer versions_table = [ [nil, nil, false], [nil, '', false], [nil, '3.9', false], [nil, '4.0', true ], [nil, '2015.1', true ], \ ['', nil, false], ['', '', false], ['', '3.9', false], ['', '4.0', true ], ['', '2015.1', true ], \ ['3.9', nil, false], ['3.9', '', false], ['3.9', '3.9', false], ['3.9', '4.0', false], ['3.9', '2015.1', false], \ ['4.0', nil, true], ['4.0', '', true], ['4.0', '3.9', true], ['4.0', '4.0', true], ['4.0', '2015.1', true], \ ['2015.1', nil, true], ['2015.1', '', true], ['2015.1', '3.9', true], ['2015.1', '4.0', true], ['2015.1', '2015.1', true], ] versions_table.each do |answers_row| it "acts with values #{answers_row} correctly" do agent1[:pe_ver] = answers_row[0] agent1[:version] = answers_row[1] agent1[:roles] = nil agent1[:type] = nil expect( subject.aio_version?(agent1) ).to be === answers_row[2] end end end context 'roles values table' do roles_table = [ [nil, false], [[], false], [['aio'], true ], [['gun'], false], [['a', 'b'], false], [['c', 'aio'], true ], ] roles_table.each do |answers_row| it "acts with values #{answers_row} correctly" do agent1[:pe_ver] = nil agent1[:version] = nil agent1[:roles] = answers_row[0] agent1[:type] = nil expect( subject.aio_version?(agent1) ).to be === answers_row[1] end end end context 'type values table' do type_table = [ [nil, false], ['', false], ['cheese', false], ['paionts', false], ['aioch', false], ['chaio', false], ['aio', true ], ['aio-', true ], ['ew-aio-ji', true ], ['id-aiot', false], ] type_table.each do |answers_row| it "acts with values #{answers_row} correctly" do agent1[:pe_ver] = nil agent1[:version] = nil agent1[:roles] = nil agent1[:type] = answers_row[0] expect( subject.aio_version?(agent1) ).to be === answers_row[1] end end end end end describe '#aio_agent?' do it 'returns false if agent_only check doesn\'t pass' do agent1[:roles] = ['agent', 'headless'] expect( subject.aio_agent?(agent1) ).to be === false end it 'returns false if aio_capable? check doesn\'t pass' do agent1[:pe_ver] = '3.8' expect( subject.aio_agent?(agent1) ).to be === false end it 'returns true if both checks pass' do agent1[:pe_ver] = '4.0' expect( subject.aio_agent?(agent1) ).to be === true end end describe '#default' do it 'returns the default host when one is specified' do @hosts = [ db, agent1, agent2, default, master] expect( subject ).to receive( :hosts ).exactly( 1 ).times.and_return( hosts ) expect( subject.default ).to be == default end it 'raises an error if there is more than one default' do @hosts = [ db, monolith, default, default ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect { subject.default }.to raise_error Beaker::DSL::FailTest end it 'and raises an error if there is no default' do @hosts = [ agent1, agent2, custom ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect { subject.default }.to raise_error Beaker::DSL::FailTest end it 'returns nil if no default and masterless is set' do @options = { :masterless => true } expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :options ).and_return( options ) expect( subject.default ).to be_nil end end describe '#add_role_def' do it 'raises an error on unsupported role format "1role"' do expect { subject.add_role_def( "1role" ) }.to raise_error ArgumentError end it 'raises an error on unsupported role format "role_!a"' do expect { subject.add_role_def( "role_!a" ) }.to raise_error ArgumentError end it 'raises an error on unsupported role format "role=="' do expect { subject.add_role_def( "role==" ) }.to raise_error ArgumentError end it 'creates new method for role "role_correct!"' do test_role = "role_correct!" subject.add_role_def( test_role ) expect( subject ).to respond_to test_role subject.class.send( :undef_method, test_role ) end it 'returns a single node for a new method for a role defined in a single node' do @hosts = [ agent1, agent2, monolith ] expect( subject ).to receive( :hosts ).and_return( hosts ) test_role = "custom_role" subject.add_role_def( test_role ) expect( subject ).to respond_to test_role expect( subject.send( test_role )).to be == @hosts[2] subject.class.send( :undef_method, test_role ) end it 'returns an array of nodes for a new method for a role defined in multiple nodes' do @hosts = [ agent1, agent2, monolith, custom ] expect( subject ).to receive( :hosts ).and_return( hosts ) test_role = "custom_role" subject.add_role_def( test_role ) expect( subject ).to respond_to test_role expect( subject.send( test_role )).to be == [@hosts[2], @hosts[3]] subject.class.send( :undef_method, test_role ) end end describe '#any_hosts_as?' do it 'returns true if a host exists, false otherwise' do @hosts = [ agent1, agent2 ] # expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :hosts ).exactly( 2 ).times.and_return( hosts ) expect( subject.any_hosts_as?( "agent" )).to be == true expect( subject.any_hosts_as?( "custom_role" )).to be == false end end end beaker-4.30.0/spec/beaker/dsl/structure_spec.rb000066400000000000000000000344731407603575700214240ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLStructure include Beaker::DSL::Structure include Beaker::DSL::Helpers::TestHelpers end describe ClassMixedWithDSLStructure do include Beaker::DSL::Assertions let (:logger) { double } let (:metadata) { @metadata ||= {} } before :each do allow( subject ).to receive(:metadata).and_return(metadata) end describe '#step' do it 'requires a name' do expect { subject.step do; end }.to raise_error ArgumentError end it 'notifies the logger' do allow( subject ).to receive( :set_current_step_name ) expect( subject ).to receive( :logger ).and_return( logger ) expect( logger ).to receive( :notify ) subject.step 'blah' end it 'yields if a block is given' do expect( subject ).to receive( :logger ).and_return( logger ).exactly(2).times allow( subject ).to receive( :set_current_step_name ) allow( logger ).to receive(:with_indent) { |&block| block.call } expect( logger ).to receive( :notify ) expect( subject ).to receive( :foo ) subject.step 'blah' do subject.foo end end it 'sets the metadata' do allow( subject ).to receive( :logger ).and_return( logger ) allow( logger ).to receive( :notify ) step_name = 'pierceBrosnanTests' subject.step step_name expect( metadata[:step][:name] ).to be === step_name end end describe '#manual_step' do context 'without exec manual test option' do let( :options ) { {} } it 'throws an error' do expect( Readline ).not_to receive( :readline ) expect { subject.manual_step 'blah' do; end }.to raise_error StandardError end end context 'with exec manual test option' do let( :options ) { {exec_manual_tests: nil} } it 'requires a name' do expect { subject.manual_step do; end }.to raise_error ArgumentError end it 'notifies the logger' do subject.instance_variable_set(:@options, options) allow( subject ).to receive( :set_current_step_name ) expect( subject ).to receive( :logger ).and_return( logger ) expect( logger ).to receive( :notify ) allow( Readline ).to receive( :readline ).and_return( 'Y') subject.manual_step 'blah' end end context 'with exec manual test option set to true' do let( :options ) { {exec_manual_tests: true} } it 'requires a name' do expect { subject.manual_step do; end }.to raise_error ArgumentError end it 'pass when user enters Y' do subject.instance_variable_set(:@options, options) allow( subject ).to receive( :set_current_step_name ) allow( subject ).to receive( :logger ).and_return( logger ) allow( logger ).to receive( :notify ) expect( Readline ).to receive( :readline ).and_return( 'Y') subject.manual_step 'blahblah' end it 'fails when user enters n and uses default error when no message is entered' do subject.instance_variable_set(:@options, options) allow( subject ).to receive( :set_current_step_name ) allow( subject ).to receive( :logger ).and_return( logger ) allow( logger ).to receive( :notify ) expect( Readline ).to receive( :readline ).and_return('n', 'step failed') expect { subject.manual_step 'blah two' do; end }.to raise_error(Beaker::DSL::FailTest, 'step failed') end end end describe '#manual_test' do context 'without exec manual test option' do let( :options ) { {} } it 'requires a name' do expect { subject.manual_test do; end }.to raise_error ArgumentError end it 'raises a skip test' do subject.instance_variable_set(:@options, options) allow( subject ).to receive( :logger ).and_return( logger ) allow( logger ).to receive( :notify ) test_name = 'random test name' expect { subject.manual_test test_name do; end }.to raise_error Beaker::DSL::SkipTest end end context 'with exec manual test option' do let( :options ) { {exec_manual_tests: true} } it 'requires a name' do expect { subject.manual_test do; end }.to raise_error ArgumentError end it 'notifies the logger' do subject.instance_variable_set(:@options, options) expect( subject ).to receive( :logger ).and_return( logger ) expect( logger ).to receive( :notify ) subject.manual_test 'blah blah' end it 'yields if a block is given' do subject.instance_variable_set(:@options, options) expect( subject ).to receive( :logger ).and_return( logger ).exactly(2).times expect( logger ).to receive( :notify ) allow( logger ).to receive(:with_indent) { |&block| block.call } expect( subject ).to receive( :foo ) subject.manual_test 'blah' do subject.foo end end it 'sets the metadata' do subject.instance_variable_set(:@options, options) allow( subject ).to receive( :logger ).and_return( logger ) allow( logger ).to receive( :notify ) test_name = 'test is setting metadata yay!' subject.manual_test test_name expect( metadata[:case][:name] ).to be === test_name end end end describe '#test_name' do it 'requires a name' do expect { subject.test_name do; end }.to raise_error ArgumentError end it 'notifies the logger' do expect( subject ).to receive( :logger ).and_return( logger ) expect( logger ).to receive( :notify ) subject.test_name 'blah' end it 'yields if a block is given' do expect( subject ).to receive( :logger ).and_return( logger ).exactly(2).times expect( logger ).to receive( :notify ) allow( logger ).to receive(:with_indent) { |&block| block.call } expect( subject ).to receive( :foo ) subject.test_name 'blah' do subject.foo end end it 'sets the metadata' do allow( subject ).to receive( :logger ).and_return( logger ) allow( logger ).to receive( :notify ) test_name = '15-05-08\'s weather is beautiful' subject.test_name test_name expect( metadata[:case][:name] ).to be === test_name end end describe '#teardown' do it 'append a block to the @teardown var' do teardown_array = double subject.instance_variable_set :@teardown_procs, teardown_array block = lambda { 'blah' } expect( teardown_array ).to receive( :<< ).with( block ) subject.teardown &block end end describe '#expect_failure' do it 'passes when a MiniTest assertion is raised' do expect( subject ).to receive( :logger ).and_return( logger ) expect( logger ).to receive( :notify ) # We changed this lambda to use the simplest assert possible; using assert_equal # caused an error in minitest 5.9.0 trying to write to the file system. block = lambda { assert(false, 'this assertion should be caught') } expect{ subject.expect_failure 'this is an expected failure', &block }.to_not raise_error end it 'passes when a Beaker assertion is raised' do expect( subject ).to receive( :logger ).and_return( logger ) expect( logger ).to receive( :notify ) block = lambda { assert_no_match('1', '1', '1 and 1 should not match') } expect{ subject.expect_failure 'this is an expected failure', &block }.to_not raise_error end it 'fails when a non-Beaker, non-MiniTest assertion is raised' do block = lambda { raise 'not a Beaker or MiniTest error' } expect{ subject.expect_failure 'this has a non-Beaker, non-MiniTest exception', &block }.to raise_error(RuntimeError, /not a Beaker or MiniTest error/) end it 'fails when no assertion is raised' do block = lambda { assert_equal('1', '1', '1 should equal 1') } expect{ subject.expect_failure 'this has no failure', &block }.to raise_error(RuntimeError, /An assertion was expected to fail, but passed/) end end describe 'confine' do let(:logger) { double.as_null_object } before do allow( subject ).to receive( :logger ).and_return( logger ) end it ':to - skips the test if there are no applicable hosts' do allow( subject ).to receive( :hosts ).and_return( [] ) allow( subject ).to receive( :hosts= ) expect( logger ).to receive( :warn ) expect( subject ).to receive( :skip_test ).with( 'No suitable hosts found with {}' ) subject.confine( :to, {} ) end it ':except - skips the test if there are no applicable hosts' do allow( subject ).to receive( :hosts ).and_return( [] ) allow( subject ).to receive( :hosts= ) expect( logger ).to receive( :warn ) expect( subject ).to receive( :skip_test ).with( 'No suitable hosts found without {}' ) subject.confine( :except, {} ) end it ':to - uses a provided host subset when no criteria is provided' do subset = ['host1', 'host2'] hosts = subset.dup << 'host3' allow( subject ).to receive( :hosts ).and_return(hosts).twice expect( subject ).to receive( :hosts= ).with( subset ) subject.confine :to, {}, subset end it ':except - excludes provided host subset when no criteria is provided' do subset = ['host1', 'host2'] hosts = subset.dup << 'host3' allow( subject ).to receive( :hosts ).and_return(hosts).twice expect( subject ).to receive( :hosts= ).with( hosts - subset ) subject.confine :except, {}, subset end it 'raises when given mode is not :to or :except' do hosts = ['host1', 'host2'] allow( subject ).to receive( :hosts ).and_return(hosts) allow( subject ).to receive( :hosts= ) expect { subject.confine( :regardless, {:thing => 'value'} ) }.to raise_error( 'Unknown option regardless' ) end it 'rejects hosts that do not meet simple hash criteria' do hosts = [ {'thing' => 'foo'}, {'thing' => 'bar'} ] expect( subject ).to receive( :hosts ).and_return( hosts ).twice expect( subject ).to receive( :hosts= ). with( [ {'thing' => 'foo'} ] ) subject.confine :to, :thing => 'foo' end it 'rejects hosts that match a list of criteria' do hosts = [ {'thing' => 'foo'}, {'thing' => 'bar'}, {'thing' => 'baz'} ] expect( subject ).to receive( :hosts ).and_return( hosts ).twice expect( subject ).to receive( :hosts= ). with( [ {'thing' => 'bar'} ] ) subject.confine :except, :thing => ['foo', 'baz'] end it 'rejects hosts when a passed block returns true' do host1 = {'platform' => 'solaris'} host2 = {'platform' => 'solaris'} host3 = {'platform' => 'windows'} ret1 = (Struct.new('Result1', :stdout)).new(':global') ret2 = (Struct.new('Result2', :stdout)).new('a_zone') hosts = [ host1, host2, host3 ] expect( subject ).to receive( :hosts ).and_return( hosts ).twice expect( subject ).to receive( :on ). with( host1, '/sbin/zonename' ). and_return( ret1 ) expect( subject ).to receive( :on ). with( host1, '/sbin/zonename' ). and_return( ret2 ) expect( subject ).to receive( :hosts= ).with( [ host1 ] ) subject.confine :to, :platform => 'solaris' do |host| subject.on( host, '/sbin/zonename' ).stdout =~ /:global/ end end it 'doesn\'t corrupt the global hosts hash when confining from a subset of hosts' do host1 = {'platform' => 'solaris', :roles => ['master']} host2 = {'platform' => 'solaris', :roles => ['agent']} host3 = {'platform' => 'windows', :roles => ['agent']} hosts = [ host1, host2, host3 ] agents = [ host2, host3 ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :hosts= ).with( [ host2, host1 ] ) confined_hosts = subject.confine :except, {:platform => 'windows'}, agents expect( confined_hosts ).to be === [ host2, host1 ] end it 'can apply multiple confines correctly' do host1 = {'platform' => 'solaris', :roles => ['master']} host2 = {'platform' => 'solaris', :roles => ['agent']} host3 = {'platform' => 'windows', :roles => ['agent']} host4 = {'platform' => 'fedora', :roles => ['agent']} host5 = {'platform' => 'fedora', :roles => ['agent']} hosts = [ host1, host2, host3, host4, host5 ] agents = [ host2, host3, host4, host5 ] expect( subject ).to receive( :hosts ).and_return( hosts ).exactly(3).times expect( subject ).to receive( :hosts= ).with( [ host1, host2, host4, host5 ] ) hosts = subject.confine :except, {:platform => 'windows'} expect( hosts ).to be === [ host1, host2, host4, host5 ] expect( subject ).to receive( :hosts= ).with( [ host4, host5, host1 ] ) hosts = subject.confine :to, {:platform => 'fedora'}, agents expect( hosts ).to be === [ host4, host5, host1 ] end end describe '#select_hosts' do let(:logger) { double.as_null_object } before do allow( subject ).to receive( :logger ).and_return( logger ) end it 'it returns an empty array if there are no applicable hosts' do hosts = [ {'thing' => 'foo'}, {'thing' => 'bar'} ] expect(subject.select_hosts( {'thing' => 'nope'}, hosts )).to be == [] end it 'selects hosts that match a list of criteria' do hosts = [ {'thing' => 'foo'}, {'thing' => 'bar'}, {'thing' => 'baz'} ] expect(subject.select_hosts( {:thing => ['foo', 'baz']}, hosts )).to be == [ {'thing' => 'foo'}, {'thing' => 'baz'} ] end it 'selects hosts when a passed block returns true' do host1 = {'platform' => 'solaris1'} host2 = {'platform' => 'solaris2'} host3 = {'platform' => 'windows'} ret1 = double('result1') allow( ret1 ).to receive( :stdout ).and_return(':global') ret2 = double('result2') allow( ret2 ).to receive( :stdout ).and_return('a_zone') hosts = [ host1, host2, host3 ] expect( subject ).to receive( :hosts ).and_return( hosts ) expect( subject ).to receive( :on ).with( host1, '/sbin/zonename' ).once.and_return( ret1 ) expect( subject ).to receive( :on ).with( host2, '/sbin/zonename' ).once.and_return( ret2 ) selected_hosts = subject.select_hosts 'platform' => 'solaris' do |host| subject.on(host, '/sbin/zonename').stdout =~ /:global/ end expect( selected_hosts ).to be == [ host1 ] end end end beaker-4.30.0/spec/beaker/dsl/test_tagging_spec.rb000066400000000000000000000225721407603575700220400ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLStructure include Beaker::DSL::TestTagging include Beaker::DSL::Helpers::TestHelpers end describe ClassMixedWithDSLStructure do include Beaker::DSL::Assertions let (:logger) { double } let (:metadata) { @metadata ||= {} } before :each do allow( subject ).to receive(:metadata).and_return(metadata) end describe '#tag' do let ( :test_tag_and ) { @test_tag_and || [] } let ( :test_tag_or ) { @test_tag_or || [] } let ( :test_tag_exclude ) { @test_tag_exclude || [] } let ( :options ) { opts = Beaker::Options::OptionsHash.new opts[:test_tag_and] = test_tag_and opts[:test_tag_or] = test_tag_or opts[:test_tag_exclude] = test_tag_exclude opts } before :each do allow( subject ).to receive( :platform_specific_tag_confines ) end it 'sets tags on the TestCase\'s metadata object' do subject.instance_variable_set(:@options, options) tags = ['pants', 'jayjay', 'moguely'] subject.tag(*tags) expect( metadata[:case][:tags] ).to be === tags end it 'lowercases the tags' do subject.instance_variable_set(:@options, options) tags_upper = ['pANTs', 'jAYJAy', 'moGUYly'] tags_lower = tags_upper.map(&:downcase) subject.tag(*tags_upper) expect( metadata[:case][:tags] ).to be === tags_lower end it 'skips the test if any of the requested tags isn\'t included in this test' do test_tags = ['pants', 'jayjay', 'moguely'] @test_tag_and = test_tags.compact.push('needed_tag_not_in_test') subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ) subject.tag(*test_tags) end it 'runs the test if all requested tags are included in this test' do @test_tag_and = ['pants_on_head', 'jayjay_jayjay', 'mo'] test_tags = @test_tag_and.compact.push('extra_asdf') subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ).never subject.tag(*test_tags) end it 'skips the test if any of the excluded tags are included in this test' do test_tags = ['ports', 'jay_john_mary', 'mog_the_dog'] @test_tag_exclude = [test_tags[0]] subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ) subject.tag(*test_tags) end it 'skips the test if an and-included & excluded tag are in this test' do test_tags = ['ports', 'jay_john_mary', 'mog_the_dog'] @test_tag_and = [test_tags[1]] @test_tag_exclude = [test_tags[0]] subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ) subject.tag(*test_tags) end it 'runs the test if none of the excluded tags are included in this test' do @test_tag_exclude = ['pants_on_head', 'jayjay_jayjay', 'mo'] test_tags = ['pants_at_head', 'jayj00_jayjay', 'motly_crew'] subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ).never subject.tag(*test_tags) end it 'skips the test if none of the OR tags are included in this test' do test_tags = ['portmanteau', 'foolios'] @test_tag_or = ['fish', 'crayons', 'parkas'] subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ) subject.tag(*test_tags) end it 'runs the test if only one of the OR tags are included in this test' do test_tags = ['portmanteau', 'foolios'] @test_tag_or = ['foolios', 'crayons', 'parkas'] subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ).never subject.tag(*test_tags) end it 'skips the test if an or-included & excluded tag are included in this test' do test_tags = ['ports', 'jay_john_mary', 'mog_the_dog'] @test_tag_or = [test_tags[1]] @test_tag_exclude = [test_tags[0]] subject.instance_variable_set(:@options, options) allow( subject ).to receive( :path ) expect( subject ).to receive( :skip_test ) subject.tag(*test_tags) end end end describe Beaker::DSL::TestTagging::PlatformTagConfiner do let ( :confines_array ) { @confines_array || [] } let ( :confiner ) { Beaker::DSL::TestTagging::PlatformTagConfiner.new( confines_array ) } describe '#initialize' do it 'transforms one entry' do platform_regex = /^ubuntu$/ tag_reason_hash = { 'tag1' => 'reason1', 'tag2' => 'reason2' } @confines_array = [ { :platform => platform_regex, :tag_reason_hash => tag_reason_hash } ] internal_hash = confiner.instance_variable_get( :@tag_confine_details_hash ) expect( internal_hash.keys() ).to include( 'tag1' ) expect( internal_hash.keys() ).to include( 'tag2' ) expect( internal_hash.keys().length() ).to be === 2 tag_reason_hash.each do |tag, reason| tag_array = internal_hash[tag] expect( tag_array.length() ).to be === 1 tag_hash = tag_array[0] expect( tag_hash[:platform_regex] ).to eql( platform_regex ) expect( tag_hash[:log_message] ).to match( /#{reason}/ ) expect( tag_hash[:type] ).to be === :except end end it 'deals with the same tag being used on multiple platforms correctly' do @confines_array = [ { :platform => /^el-/, :tag_reason_hash => { 'tag1' => 'reason el 1', 'tag2' => 'reason2' } }, { :platform => /^cisco-/, :tag_reason_hash => { 'tag1' => 'reason cisco 1', 'tag3' => 'reason3' } } ] internal_hash = confiner.instance_variable_get( :@tag_confine_details_hash ) expect( internal_hash.keys() ).to include( 'tag1' ) expect( internal_hash.keys() ).to include( 'tag2' ) expect( internal_hash.keys() ).to include( 'tag3' ) expect( internal_hash.keys().length() ).to be === 3 shared_tag_array = internal_hash['tag1'] expect( shared_tag_array.length() ).to be === 2 platform_el_found = false platform_cisco_found = false shared_tag_array.each do |confine_details| case confine_details[:log_message] when /\ el\ 1/ platform_el_found = true platform_to_match = /^el-/ reason_to_match = /reason\ el\ 1/ when /\ cisco\ 1/ platform_cisco_found = true platform_to_match = /^cisco-/ reason_to_match = /reason\ cisco\ 1/ else log_msg = "unexpected log message for confine_details: " log_msg << confine_details[:log_message] fail( log_msg ) end expect( confine_details[:platform_regex] ).to eql( platform_to_match ) expect( confine_details[:log_message] ).to match( reason_to_match ) end expect( platform_el_found ).to be === true expect( platform_cisco_found ).to be === true end end describe '#confine_details' do it 'returns an empty array if no tags match' do fake_confine_details_hash = { 'tag1' => [ {:type => 1}, {:type => 2} ]} confiner.instance_variable_set( :@tag_confine_details_hash, fake_confine_details_hash ) expect( confiner.confine_details( [ 'tag2', 'tag3' ] ) ).to be === [] end context 'descriminates on tag name' do fake_confine_details_hash = { 'tag0' => [ 10, 20, 30, 40 ], 'tag1' => [ 41, 51, 61, 71 ], 'tag2' => [ 22, 32, 42, 52 ], 'tag3' => [ 63, 73, 83, 93 ], 'tag4' => [ 34, 44, 54, 64 ], } key_combos_to_test = fake_confine_details_hash.keys.map { |key| [key] } key_combos_to_test << [ 'tag0', 'tag2' ] key_combos_to_test << [ 'tag1', 'tag4' ] key_combos_to_test << [ 'tag2', 'tag3', 'tag4' ] key_combos_to_test << fake_confine_details_hash.keys() before :each do confiner.instance_variable_set( :@tag_confine_details_hash, fake_confine_details_hash ) end key_combos_to_test.each do |key_combo_to_have| it "selects key(s) #{key_combo_to_have} from #{fake_confine_details_hash.keys}" do haves = [] key_combo_to_have.each do |key_to_have| haves += fake_confine_details_hash[key_to_have] end keys_not_to_have = fake_confine_details_hash.keys.reject { |key_trial| key_combo_to_have.include?( key_trial ) } have_nots = [] keys_not_to_have.each do |key_not_to_have| have_nots += fake_confine_details_hash[key_not_to_have] end details = confiner.confine_details( key_combo_to_have ) have_nots.each do |confine_details| expect( details ).to_not include( confine_details ) end haves.each do |confine_details| expect( details ).to include( confine_details ) end end end end end endbeaker-4.30.0/spec/beaker/dsl/wrappers_spec.rb000066400000000000000000000101571407603575700212200ustar00rootroot00000000000000require 'spec_helper' class ClassMixedWithDSLWrappers include Beaker::DSL::Wrappers end describe ClassMixedWithDSLWrappers do describe '#host_command' do it 'delegates to HostCommand.new' do expect( Beaker::HostCommand ).to receive( :new ).with( 'blah' ) subject.host_command( 'blah' ) end end describe '#powershell' do it 'should pass "powershell.exe -Command " to Command' do command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'") expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command Set-Content -path 'fu.txt' -value 'fu'"] expect( command.options ).to be === {} end it 'should merge the arguments provided with the defaults' do command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'", {'ExecutionPolicy' => 'Unrestricted'}) expect( command.command).to be === 'powershell.exe' expect( command.args ).to be === ["-ExecutionPolicy Unrestricted", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command Set-Content -path 'fu.txt' -value 'fu'"] expect( command.options ).to be === {} end it 'should use EncodedCommand when EncodedCommand => true' do cmd = "Set-Content -path 'fu.txt' -value 'fu'" cmd = subject.encode_command(cmd) command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'", {'EncodedCommand' => true}) expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand #{cmd}"] expect( command.options ).to be === {} end it 'should use EncodedCommand when EncodedCommand => ""' do cmd = "Set-Content -path 'fu.txt' -value 'fu'" cmd = subject.encode_command(cmd) command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'", {'EncodedCommand' => ""}) expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand #{cmd}"] expect( command.options ).to be === {} end it 'should use EncodedCommand when EncodedCommand => nil' do cmd = "Set-Content -path 'fu.txt' -value 'fu'" cmd = subject.encode_command(cmd) command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'", {'EncodedCommand' => nil}) expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand #{cmd}"] expect( command.options ).to be === {} end it 'should not use EncodedCommand when EncodedCommand => false' do command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'", {'EncodedCommand' => false}) expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command Set-Content -path 'fu.txt' -value 'fu'"] expect( command.options ).to be === {} end it 'should not use EncodedCommand when EncodedCommand not present' do command = subject.powershell("Set-Content -path 'fu.txt' -value 'fu'", {}) expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command Set-Content -path 'fu.txt' -value 'fu'"] expect( command.options ).to be === {} end it 'has no -Command/-EncodedCommand when command is empty' do command = subject.powershell("", {"File" => 'myfile.ps1'}) expect(command.command ).to be === 'powershell.exe' expect( command.args).to be === ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-File myfile.ps1"] expect( command.options ).to be === {} end end end beaker-4.30.0/spec/beaker/host/000077500000000000000000000000001407603575700162055ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/host/aix_spec.rb000066400000000000000000000032411407603575700203250ustar00rootroot00000000000000require 'spec_helper' module Aix describe Host do let(:options) { @options ? @options : {} } let(:platform) { if @platform { :platform => Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'aix-vers-arch-extra' ) } end } let(:host) { make_host( 'name', options.merge(platform) ) } describe '#ssh_service_restart' do it 'invokes the correct commands on the host' do expect( Beaker::Command ).to receive( :new ).with( 'stopsrc -g ssh' ).once.ordered expect( Beaker::Command ).to receive( :new ).with( 'startsrc -g ssh' ).once.ordered host.ssh_service_restart end end describe '#ssh_permit_user_environment' do it 'calls echo to set PermitUserEnvironment' do expect( Beaker::Command ).to receive( :new ).with( /^echo\ / ).once.ordered allow( host ).to receive( :ssh_service_restart ) host.ssh_permit_user_environment end it 'uses the correct ssh config file' do expect( Beaker::Command ).to receive( :new ).with( /#{Regexp.escape(' >> /etc/ssh/sshd_config')}$/ ).once allow( host ).to receive( :ssh_service_restart ) host.ssh_permit_user_environment end end describe '#reboot' do it 'invokes the correct command on the host' do expect( Beaker::Command ).to receive( :new ).with( 'shutdown -Fr' ).once host.reboot end end describe '#get_ip' do it 'invokes the correct command on the host' do expect( host ).to receive( :execute ).with( /^ifconfig\ \-a\ inet\|\ / ).once.and_return( '' ) host.get_ip end end end endbeaker-4.30.0/spec/beaker/host/cisco_spec.rb000066400000000000000000000256741407603575700206620ustar00rootroot00000000000000require 'spec_helper' module Cisco describe Host do let(:options) { @options ? @options : { :user => 'root', } } let(:platform) { if @platform { :platform => Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'cisco_nexus-vers-arch-extra' ) } end } let(:host) { make_host( 'name', options.merge(platform) ) } describe '#prepend_commands' do context 'for cisco_nexus-7' do before :each do @platform = 'cisco_nexus-7-x86_64' end it 'starts with sourcing the /etc/profile script' do answer_correct = 'source /etc/profile;' answer_test = host.prepend_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'uses `sudo` if not root' do @options = { :user => 'notroot' } answer_correct = "source /etc/profile; sudo -E sh -c \"" answer_test = host.prepend_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'ends with the :vrf host parameter' do vrf_answer = 'vrf_answer_135246' @options = { :vrf => vrf_answer, } answer_test = host.prepend_commands( 'fake_command' ) expect( answer_test ).to match( /ip netns exec #{vrf_answer}$/ ) end it 'guards against "vsh" usage (scenario we never want prefixing)' do answer_user_pc = 'pc_param_unchanged_13584' answer_test = host.prepend_commands( 'fake/vsh/command', answer_user_pc ) expect( answer_test ).to be === answer_user_pc end it 'guards against "ntpdate" usage (we dont want prefixing on nexus)' do answer_user_pc = 'user_pc_param_54321' answer_test = host.prepend_commands( 'fake/ntpdate/command', answer_user_pc ) expect( answer_test ).to be === answer_user_pc end it 'retains user-specified prepend commands when adding vrf' do @options = { :vrf => 'fakevrf', :user => 'root', } answer_prepend_commands = 'prepend' answer_correct = 'source /etc/profile;ip netns exec fakevrf prepend' answer_test = host.prepend_commands( 'fake_command', answer_prepend_commands ) expect( answer_test ).to be === answer_correct end end context 'for cisco_ios_xr-6' do before :each do @platform = 'cisco_ios_xr-6-x86_64' end it 'starts with sourcing the /etc/profile script' do answer_correct = 'source /etc/profile;' answer_test = host.prepend_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'does use the :vrf host parameter if provided' do @options = { :vrf => 'tpnns' } answer_test = host.prepend_commands( 'fake_command' ) expect( answer_test ).to match( /ip netns exec tpnns/ ) end it 'does not guard "ntpdate" usage' do answer_user_pc = 'user_pc_param_54321' answer_correct = 'source /etc/profile;user_pc_param_54321' answer_test = host.prepend_commands( 'fake/ntpdate/command', answer_user_pc ) expect( answer_test ).to be === answer_correct end it 'retains user-specified prepend commands when adding vrf' do @options = { :vrf => 'fakevrf', :user => 'root', } answer_prepend_commands = 'prepend' answer_correct = 'source /etc/profile;ip netns exec fakevrf prepend' answer_test = host.prepend_commands( 'fake_command', answer_prepend_commands ) expect( answer_test ).to be === answer_correct end end end describe '#append_commands' do context 'for cisco_nexus-7' do before :each do @platform = 'cisco_nexus-7-x86_64' @options = { :user => 'non_root' } end it 'appends `"` for commands' do answer_correct = '"' answer_test = host.append_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'returns nil for root user commands' do @options = { :user => 'root' } answer_correct = nil answer_test = host.append_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'returns nil when vsh command' do answer_correct = nil answer_test = host.append_commands( '/isan/bin/vsh -c foo' ) expect( answer_test ).to be === answer_correct end it 'returns `"` when command contains vsh' do answer_correct = '"' answer_test = host.append_commands( 'fake_command -c foo vsh' ) expect( answer_test ).to be === answer_correct end it 'returns nil when ntpdate command' do answer_correct = nil answer_test = host.append_commands( 'fake/ntpdate/command foo' ) expect( answer_test ).to be === answer_correct end end context 'for cisco_ios_xr-6' do before :each do @platform = 'cisco_ios_xr-6-x86_64' @options = { :user => 'non_root' } end it 'appends `"` for commands' do answer_correct = '"' answer_test = host.append_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'returns nil for root user commands' do @options = { :user => 'root' } answer_correct = nil answer_test = host.append_commands( 'fake_command' ) expect( answer_test ).to be === answer_correct end it 'returns nil when vsh command' do answer_correct = nil answer_test = host.append_commands( '/isan/bin/vsh -c foo' ) expect( answer_test ).to be === answer_correct end it 'returns `"` when command contains vsh' do answer_correct = '"' answer_test = host.append_commands( 'fake_command -c foo vsh' ) expect( answer_test ).to be === answer_correct end it 'returns nil when ntpdate command' do answer_correct = nil answer_test = host.append_commands( 'fake/ntpdate/command foo' ) expect( answer_test ).to be === answer_correct end end end describe '#environment_string' do context 'for cisco_nexus-7' do before :each do @platform = 'cisco_nexus-7-x86_64' end it 'uses `export` if root' do @options = { :user => 'root' } env_map = { 'PATH' => '/opt/pants/2' } answer_correct = ' export PATH="/opt/pants/2";' answer_test = host.environment_string( env_map ) expect( answer_test ).to be === answer_correct end it 'ends with a semi-colon' do env_map = { 'PATH' => '/opt/pants/3' } answer_test = host.environment_string( env_map ) expect( answer_test ).to match( /\;$/ ) end it 'turns env maps into paired strings correctly' do @options = { :user => 'root' } env_map = { 'var1' => 'ans1', 'VAR2' => 'ans2' } answer_correct = ' export var1="ans1" VAR1="ans1" VAR2="ans2";' answer_test = host.environment_string( env_map ) expect( answer_test ).to be === answer_correct end end context 'for cisco_ios_xr-6' do before :each do @platform = 'cisco_ios_xr-6-x86_64' end it 'uses `sudo` if not root' do @options = { :user => 'notroot' } env_map = { 'PATH' => '/opt/pants/2' } answer_correct = ' env PATH="/opt/pants/2"' answer_test = host.environment_string( env_map ) expect( answer_test ).to be === answer_correct end it 'uses `env` if root' do @options = { :user => 'root' } env_map = { 'PATH' => '/opt/pants/1' } answer_correct = ' env PATH="/opt/pants/1"' answer_test = host.environment_string( env_map ) expect( answer_test ).to be === answer_correct end it 'does not end with a semi-colon' do env_map = { 'PATH' => '/opt/pants/3' } answer_test = host.environment_string( env_map ) expect( answer_test ).not_to match( /\;$/ ) end it 'turns env maps into paired strings correctly' do @options = { :user => 'root' } env_map = { 'VAR1' => 'ans1', 'var2' => 'ans2' } answer_correct = ' env VAR1="ans1" var2="ans2" VAR2="ans2"' answer_test = host.environment_string( env_map ) expect( answer_test ).to be === answer_correct end end end describe '#package_config_dir' do it 'returns correctly for cisco platforms' do @platform = 'cisco_nexus-7-x86_64' expect( host.package_config_dir ).to be === '/etc/yum/repos.d/' end end describe '#repo_type' do it 'returns correctly for cisco platforms' do @platform = 'cisco_nexus-7-x86_64' expect( host.repo_type ).to be === 'rpm' end end describe '#validate_setup' do context 'on the cisco_nexus-7 platform' do before :each do @platform = 'cisco_nexus-7-x86_64' end it 'errors when no :vrf value is provided' do expect { host.validate_setup }.to raise_error( ArgumentError, /provided\ with\ a\ \:vrf\ value/ ) end it 'errors when no :user value is provided' do @options = { :vrf => 'fake_vrf', :user => nil, } expect { host.validate_setup }.to raise_error( ArgumentError, /provided\ with\ a\ \:user\ value/ ) end it 'does nothing if the host is setup correctly' do @options = { :vrf => 'fake_vrf', :user => 'notroot', } validate_test = host.validate_setup expect( validate_test ).to be_nil end end context 'on the cisco_ios_xr-6 platform' do before :each do @platform = 'cisco_ios_xr-6-x86_64' end it 'does nothing if no :vrf value is provided' do @options = { :user => 'notroot', } validate_test = host.validate_setup expect( validate_test ).to be_nil end it 'errors when no user is provided' do @options = { :vrf => 'fake_vrf', :user => nil, } expect { host.validate_setup }.to raise_error( ArgumentError, /provided\ with\ a\ \:user\ value/ ) end it 'does nothing if the host is setup correctly' do @options = { :vrf => 'fake_vrf', :user => 'notroot', } validate_test = host.validate_setup expect( validate_test ).to be_nil end end end end end beaker-4.30.0/spec/beaker/host/eos_spec.rb000066400000000000000000000041161407603575700203340ustar00rootroot00000000000000require 'spec_helper' module Eos describe Host do let(:options) { @options ? @options : {} } let(:platform) { if @platform { :platform => Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'eos-vers-arch-extra' ) } end } let(:host) { make_host( 'name', options.merge(platform) ) } describe '#puppet_agent_dev_package_info' do it 'raises an error if puppet_collection isn\'t passed' do expect { host.puppet_agent_dev_package_info(nil, 'maybe') }.to raise_error(ArgumentError) end it 'raises as error if puppet_agent_version isn\'t passed' do expect { host.puppet_agent_dev_package_info('maybe', nil) }.to raise_error(ArgumentError) end it 'returns two strings that include the passed parameters' do return1, return2 = host.puppet_agent_dev_package_info('pc1', 'pav1') expect( return1 ).to match(/pc1/) expect( return2 ).to match(/pav1/) end it 'gets the correct file type' do _, return2 = host.puppet_agent_dev_package_info('pc1', 'pav1') expect( return2 ).to match(/swix/) end end describe '#get_remote_file' do it 'calls enable first' do expect( host ).to receive( :execute ).with(/enable/) host.get_remote_file( 'remote_url' ) end it 'begins second line with the copy command' do expect( host ).to receive( :execute ).with(/\ncopy/) host.get_remote_file( 'remote_url' ) end it 'ends second line with particular extension location' do expect( host ).to receive( :execute ).with(/extension\:\'$/) host.get_remote_file( 'remote_url' ) end end describe '#install_from_file' do it 'calls enable first' do expect( host ).to receive( :execute ).with(/enable/) host.install_from_file( 'local_file' ) end it 'begins second line with the extension command' do expect( host ).to receive( :execute ).with(/\nextension/) host.install_from_file( 'local_file' ) end end end endbeaker-4.30.0/spec/beaker/host/freebsd/000077500000000000000000000000001407603575700176175ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/host/freebsd/exec_spec.rb000066400000000000000000000017041407603575700221040ustar00rootroot00000000000000require 'spec_helper' module Beaker describe FreeBSD::Exec do class FreeBSDExecTest include FreeBSD::Exec def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { FreeBSDExecTest.new(opts, logger) } context "echo_to_file" do it "runs the correct echo command" do 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('') expect( instance ).to receive(:exec).with('').and_return(generate_result("hello", {:exit_code => 0})) instance.echo_to_file('127.0.0.1\tlocalhost localhost.localdomain\n10.255.39.23\tfreebsd-10-x64\n', '/etc/hosts') end end end end beaker-4.30.0/spec/beaker/host/freebsd/pkg_spec.rb000066400000000000000000000077301407603575700217460ustar00rootroot00000000000000require 'spec_helper' module Beaker describe FreeBSD::Pkg do class FreeBSDPkgTest include FreeBSD::Pkg def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end def exec #noop end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { FreeBSDPkgTest.new(opts, logger) } let(:cond) do 'TMPDIR=/dev/null ASSUME_ALWAYS_YES=1 PACKAGESITE=file:///nonexist pkg info -x "pkg(-devel)?\\$" > /dev/null 2>&1' end context "pkg_info_patten" do it "returns correct patterns" do expect( instance.pkg_info_pattern('rsync') ).to eq '^rsync-[0-9][0-9a-zA-Z_\\.,]*$' end end context "check_pkgng_sh" do it { expect( instance.check_pkgng_sh ).to eq cond } end context "pkgng_active?" do it "returns true if pkgng is available" do expect( instance ).to receive(:check_pkgng_sh).once.and_return("do you have pkgng?") expect( Beaker::Command ).to receive(:new).with("/bin/sh -c 'do you have pkgng?'", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('',{:accept_all_exit_codes => true}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.pkgng_active? ).to be true end it "returns false if pkgng is unavailable" do expect( instance ).to receive(:check_pkgng_sh).once.and_return("do you have pkgng?") expect( Beaker::Command ).to receive(:new).with("/bin/sh -c 'do you have pkgng?'", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('',{:accept_all_exit_codes => true}).and_return(generate_result("hello", {:exit_code => 127})) expect( instance.pkgng_active? ).to be false end end context "install_package" do context "without pkgng" do it "runs the correct install command" do expect( instance ).to receive(:pkgng_active?).once.and_return(false) expect( Beaker::Command ).to receive(:new).with("pkg_add -r rsync", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) instance.install_package('rsync') end end context "with pkgng" do it "runs the correct install command" do expect( instance ).to receive(:pkgng_active?).once.and_return(true) expect( Beaker::Command ).to receive(:new).with("pkg install -y rsync", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) instance.install_package('rsync') end end end context "check_for_package" do context "without pkgng" do it "runs the correct checking command" do expect( instance ).to receive(:pkgng_active?).once.and_return(false) expect( Beaker::Command ).to receive(:new).with("pkg_info -Ix '^rsync-[0-9][0-9a-zA-Z_\\.,]*$'", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {:accept_all_exit_codes => true}).and_return(generate_result("hello", {:exit_code => 0})) instance.check_for_package('rsync') end end context "with pkgng" do it "runs the correct checking command" do expect( instance ).to receive(:pkgng_active?).once.and_return(true) expect( Beaker::Command ).to receive(:new).with("pkg info rsync", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {:accept_all_exit_codes => true}).and_return(generate_result("hello", {:exit_code => 0})) instance.check_for_package('rsync') end end end end end beaker-4.30.0/spec/beaker/host/mac/000077500000000000000000000000001407603575700167455ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/host/mac/exec_spec.rb000066400000000000000000000020641407603575700212320ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Mac::Exec do class MacExecTest include Mac::Exec def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { MacExecTest.new(opts, logger) } describe '#selinux_enabled?' do it 'does not call selinuxenabled' do expect(Beaker::Command).not_to receive(:new).with("sudo selinuxenabled") expect(instance).not_to receive(:exec).with(0, :accept_all_exit_codes => true) expect(instance.selinux_enabled?).to be === false end end describe '#modified_at' do it 'calls execute with touch and timestamp' do time = '190101010000' path = '/path/to/file' expect( instance ).to receive(:execute).with("touch -mt #{time} #{path}").and_return(0) instance.modified_at(path, time) end end end end beaker-4.30.0/spec/beaker/host/mac/group_spec.rb000066400000000000000000000071041407603575700214420ustar00rootroot00000000000000require 'spec_helper' class MacGroupTest include Mac::Group end describe MacGroupTest do let( :puppet1 ) do <<-EOS name: puppet1 password: * gid: 55 EOS end let( :puppet2 ) do <<-EOS name: puppet2 password: * gid: 56 EOS end let( :dscacheutil_list ) do <<-EOS #{puppet1} #{puppet2} EOS end let( :command ) { 'ls' } let( :host ) { double.as_null_object } let( :result ) { Beaker::Result.new( host, command ) } describe '#group_list' do it 'returns group names list correctly' do result.stdout = dscacheutil_list expect( subject ).to receive( :execute ).and_yield(result) expect( subject.group_list ).to be === ['puppet1', 'puppet2'] end it 'yields correctly with the result object' do result.stdout = dscacheutil_list expect( subject ).to receive( :execute ).and_yield(result) subject.group_list { |result| expect( result.stdout ).to be === dscacheutil_list } end end describe '#group_get' do it 'fails if a name line isn\'t included' do result.stdout = '' group_name = 'any_name' expect( subject ).to receive( :execute ).and_yield(result) expect { subject.group_get(group_name) }.to raise_error(MiniTest::Assertion, "failed to get group #{group_name}") end it 'parses mac dscacheutil output into /etc/group format correctly' do result.stdout = puppet1 expect( subject ).to receive( :execute ).and_yield(result) subject.group_get('puppet1') do |answer| expect(answer).to be === 'puppet1:*:55' end end end describe '#group_gid' do it 'parses mac dscacheutil output into the gid correctly' do result.stdout = puppet1 expect( subject ).to receive( :execute ).and_yield(result) expect( subject.group_gid(puppet1) ).to be === '55' end it 'returns -1 if gid not found' do result.stdout = '' expect( subject ).to receive( :execute ).and_yield(result) expect( subject.group_gid(puppet1) ).to be === -1 end end describe '#group_present' do it 'returns group existence without running create command if it already exists' do result.stdout = puppet1 expect( subject ).to receive( :execute ).once.and_yield(result) expect( subject ).not_to receive( :gid_next ) subject.group_present( 'puppet1' ) end it 'runs correct create command if group does not exist' do result.stdout = '' gid = 512 name = "madeup_group" expect( subject ).to receive( :gid_next ).and_return(gid) expect( subject ).to receive( :execute ).once.ordered.and_yield(result) expect( subject ).to receive( :execute ).with("dscl . create /Groups/#{name} && dscl . create /Groups/#{name} PrimaryGroupID #{gid}").once.ordered subject.group_present( name ) end it 'makes the correct call to dscacheutil' do result.stdout = puppet1 expect( subject ).to receive( :execute ).with( /^dscacheutil\ \-q\ group\ \-a\ name\ / ).once.and_yield(result) expect( subject ).not_to receive( :gid_next ) subject.group_present( 'puppet1' ) end end describe '#group_absent' do it 'calls execute to run logic' do name = "main_one" expect( subject ).to receive( :execute ).once.with("if dscl . -list /Groups/#{name}; then dscl . -delete /Groups/#{name}; fi", {}) subject.group_absent( name ) end end describe '#gid_next' do it 'returns the next ID given' do n = 10 expect( subject ).to receive( :execute ).and_return("#{n}") expect( subject.gid_next ).to be === n + 1 end end end beaker-4.30.0/spec/beaker/host/mac/user_spec.rb000066400000000000000000000062271407603575700212710ustar00rootroot00000000000000require 'spec_helper' class MacUserTest include Mac::User end describe MacUserTest do let( :puppet1 ) do <<-EOS name: puppet1 password: * uid: 67 gid: 234 dir: /Users/puppet1 shell: /bin/bash gecos: Unprivileged User EOS end let( :puppet2 ) do <<-EOS name: puppet2 password: * uid: 68 gid: 235 dir: /Users/puppet2 shell: /bin/sh gecos: puppet EOS end let( :dscacheutil_list ) do <<-EOS #{puppet1} #{puppet2} EOS end let( :etc_passwd_line ) do "puppet1:*:67:234::0:0:puppet1:/Users/puppet1:/bin/sh" end let( :command ) { 'ls' } let( :host ) { double.as_null_object } let( :result ) { Beaker::Result.new( host, command ) } describe '#user_list' do it 'returns user names list correctly' do result.stdout = dscacheutil_list expect( subject ).to receive( :execute ).and_yield(result) expect( subject.user_list ).to be === ['puppet1', 'puppet2'] end it 'yields correctly with the result object' do result.stdout = dscacheutil_list expect( subject ).to receive( :execute ).and_yield(result) subject.user_list { |result| expect( result.stdout ).to be === dscacheutil_list } end end describe '#user_get' do it 'fails if a name line isn\'t included' do result.stdout = '' user_name = 'any_name' expect( subject ).to receive( :execute ).and_yield(result) expect { subject.user_get(user_name) }.to raise_error(MiniTest::Assertion, "failed to get user #{user_name}") end it 'yields correctly with the result object' do result.stdout = etc_passwd_line expect( subject ).to receive( :execute ).and_yield(result) subject.user_get('puppet1') do |result| expect( result.stdout ).to be === etc_passwd_line end end end describe '#user_present' do it 'returns user existence without running create command if it already exists' do result.stdout = puppet1 expect( subject ).to receive( :execute ).once.and_yield(result) subject.user_present( 'puppet1' ) end it 'runs correct create command if group does not exist' do result.stdout = '' uid = 512 gid = 1007 name = "madeup_user" expect( subject ).to receive( :uid_next ).and_return(uid) expect( subject ).to receive( :gid_next ).and_return(gid) expect( subject ).to receive( :execute ).once.ordered.and_yield(result) expect( subject ).to receive( :execute ).once.ordered subject.user_present( name ) end end describe '#user_absent' do it 'calls execute to run logic' do name = "main_one" expect( subject ).to receive( :execute ).once.with("if dscl . -list /Users/#{name}; then dscl . -delete /Users/#{name}; fi", {}) subject.user_absent( name ) end end describe '#uid_next' do it 'returns the next ID given' do n = 117 expect( subject ).to receive( :execute ).and_return("#{n}") expect( subject.uid_next ).to be === n + 1 end end describe '#gid_next' do it 'returns the next ID given' do n = 843 expect( subject ).to receive( :execute ).and_return("#{n}") expect( subject.gid_next ).to be === n + 1 end end end beaker-4.30.0/spec/beaker/host/mac_spec.rb000066400000000000000000000077421407603575700203160ustar00rootroot00000000000000require 'spec_helper' module Mac describe Host do let(:options) { @options ? @options : {} } let(:platform) { if @platform { :platform => Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'osx-10.12-x86_64' ) } end } let(:host) { make_host( 'name', options.merge(platform) ) } describe '#puppet_agent_dev_package_info' do it 'raises an error if puppet_collection isn\'t passed' do expect { host.puppet_agent_dev_package_info(nil, 'maybe', :download_url => '') }.to raise_error(ArgumentError) end it 'raises an error if puppet_agent_version isn\'t passed' do expect { host.puppet_agent_dev_package_info('maybe', nil, :download_url => '') }.to raise_error(ArgumentError) end it 'raises an error if opts[:download_url] isn\'t passed' do expect { host.puppet_agent_dev_package_info('', '') }.to raise_error(ArgumentError) end it 'returns two strings that include the passed parameters' do allow( host ).to receive( :link_exists? ) { true } return1, return2 = host.puppet_agent_dev_package_info( 'pc1', 'pav1', :download_url => '' ) expect( return1 ).to match( /pc1/ ) expect( return2 ).to match( /pav1/ ) end it 'gets the correct file type' do allow( host ).to receive( :link_exists? ) { true } _, return2 = host.puppet_agent_dev_package_info( 'pc2', 'pav2', :download_url => '' ) expect( return2 ).to match( /\.dmg$/ ) end it 'adds the version dot correctly if not supplied' do @platform = 'osx-10.12-x86_64' allow( host ).to receive( :link_exists? ) { true } release_path_end, release_file = host.puppet_agent_dev_package_info( 'PC3', 'pav3', :download_url => '' ) expect( release_path_end ).to match( /10\.12/ ) expect( release_file ).to match( /10\.12/ ) end it 'runs the correct install for osx platforms (newest link format)' do allow( host ).to receive( :link_exists? ) { true } release_path_end, release_file = host.puppet_agent_dev_package_info( 'PC4', 'pav4', :download_url => '' ) # verify the mac package name starts the name correctly expect( release_file ).to match( /^puppet-agent-pav4-/ ) # verify the "newest hotness" is set correctly for the end of the mac package name expect( release_file ).to match( /#{Regexp.escape("-1.osx10.12.dmg")}$/ ) # verify the release path end is set correctly expect( release_path_end ).to be === "apple/10.12/PC4/x86_64" end it 'runs the correct install for osx platforms (new link format)' do allow( host ).to receive( :link_exists? ).and_return( false, true ) release_path_end, release_file = host.puppet_agent_dev_package_info( 'PC7', 'pav7', :download_url => '' ) # verify the mac package name starts the name correctly expect( release_file ).to match( /^puppet-agent-pav7-/ ) # verify the "new hotness" is set correctly for the end of the mac package name expect( release_file ).to match( /#{Regexp.escape("-1.sierra.dmg")}$/ ) # verify the release path end isn't changed in the "new hotness" case expect( release_path_end ).to be === "apple/10.12/PC7/x86_64" end it 'runs the correct install for osx platforms (old link format)' do allow( host ).to receive( :link_exists? ) { false } release_path_end, release_file = host.puppet_agent_dev_package_info( 'PC8', 'pav8', :download_url => '' ) # verify the mac package name starts the name correctly expect( release_file ).to match( /^puppet-agent-pav8-/ ) # verify the old way is set correctly for the end of the mac package name expect( release_file ).to match( /#{Regexp.escape("-osx-10.12-x86_64.dmg")}$/ ) # verify the release path end is set correctly to the older method expect( release_path_end ).to be === "apple/PC8" end end end endbeaker-4.30.0/spec/beaker/host/pswindows/000077500000000000000000000000001407603575700202425ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/host/pswindows/exec_spec.rb000066400000000000000000000122371407603575700225320ustar00rootroot00000000000000require 'spec_helper' module Beaker describe PSWindows::Exec do class PSWindowsExecTest include PSWindows::Exec def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { PSWindowsExecTest.new(opts, logger) } context "rm" do it "deletes" do path = '/path/to/delete' corrected_path = '\\path\\to\\delete' expect(instance).to receive(:execute).with(%(del /s /q "#{corrected_path}")).and_return(0) expect(instance.rm_rf(path)).to eq(0) end end context 'mv' do let(:origin) { '/origin/path/of/content' } let(:destination) { '/destination/path/of/content' } it 'rm first' do expect(instance).to receive(:execute).with("del /s /q \"\\destination\\path\\of\\content\"").and_return(0) expect(instance).to receive(:execute).with("move /y #{origin.gsub(/\//, '\\')} #{destination.gsub(/\//, '\\')}").and_return(0) expect(instance.mv(origin, destination)).to eq(0) end it 'does not rm' do expect( instance ).to receive(:execute).with("move /y #{origin.gsub(/\//, '\\')} #{destination.gsub(/\//, '\\')}").and_return(0) expect( instance.mv(origin, destination, false) ).to be === 0 end end describe '#modified_at' do before do allow(instance).to receive(:execute).and_return(stdout) end context 'file exists' do let(:stdout) { 'True' } it 'sets the modified_at date' do file = 'C:\path\to\file' expect(instance).to receive(:execute).with("powershell Test-Path #{file} -PathType Leaf") expect(instance).to receive(:execute).with( "powershell (gci C:\\path\\to\\file).LastWriteTime = Get-Date -Year '1970'-Month '1'-Day '1'-Hour '0'-Minute '0'-Second '0'" ) instance.modified_at(file, '197001010000') end end context 'file does not exist' do let(:stdout) { 'False' } it 'creates it and sets the modified_at date' do file = 'C:\path\to\file' expect(instance).to receive(:execute).with("powershell Test-Path #{file} -PathType Leaf") expect(instance).to receive(:execute).with("powershell New-Item -ItemType file #{file}") expect(instance).to receive(:execute).with( "powershell (gci C:\\path\\to\\file).LastWriteTime = Get-Date -Year '1970'-Month '1'-Day '1'-Hour '0'-Minute '0'-Second '0'" ) instance.modified_at(file, '197001010000') end end end describe '#environment_string' do let(:host) { {'pathseparator' => ':'} } it 'returns a blank string if theres no env' do expect( instance.environment_string( {} ) ).to be == '' end it 'takes an env hash with var_name/value pairs' do expect( instance.environment_string( {:HOME => '/', :http_proxy => 'http://foo'} ) ). to be == 'set "HOME=/" && set "http_proxy=http://foo" && set "HTTP_PROXY=http://foo" && ' end it 'takes an env hash with var_name/value[Array] pairs' do expect( instance.environment_string( {:LD_PATH => ['/', '/tmp']}) ). to be == "set \"LD_PATH=/:/tmp\" && " end end describe '#which' do before do allow(instance).to receive(:execute) .with(where_command, :accept_all_exit_codes => true).and_return(result) end let(:where_command) { "cmd /C \"where ruby\"" } context 'when only the environment variable PATH is used' do let(:result) { "C:\\Ruby26-x64\\bin\\ruby.exe" } it 'returns the correct path' do response = instance.which('ruby') expect(response).to eq(result) end end context 'when command is not found' do let(:where_command) { "cmd /C \"where unknown\"" } let(:result) { '' } it 'return empty string if command is not found' do response = instance.which('unknown') expect(response).to eq(result) end end end describe '#mkdir_p' do let(:dir_path) { "C:\\tmpdir\\my_dir" } let(:beaker_command) { instance_spy(Beaker::Command) } let(:command) {"-Command New-Item -Path '#{dir_path}' -ItemType 'directory'"} let(:result) { instance_spy(Beaker::Result) } before do allow(Beaker::Command).to receive(:new). with('powershell.exe', array_including(command)).and_return(beaker_command) allow(instance).to receive(:exec).with(beaker_command, :acceptable_exit_codes => [0, 1]).and_return(result) end it 'returns true and creates folder structure' do allow(result).to receive(:exit_code).and_return(0) expect(instance.mkdir_p(dir_path)).to be(true) end it 'returns false if failed to create directory structure' do allow(result).to receive(:exit_code).and_return(1) expect(instance.mkdir_p(dir_path)).to be(false) end end end end beaker-4.30.0/spec/beaker/host/pswindows/file_spec.rb000066400000000000000000000074411407603575700225260ustar00rootroot00000000000000require 'spec_helper' module Beaker describe PSWindows::File do class PSWindowsFileTest include PSWindows::File include Beaker::DSL::Wrappers def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { PSWindowsFileTest.new(opts, logger) } describe '#cat' do let(:path) { '/path/to/cat' } let(:content) { 'file content' } it 'reads output for file' do expect(instance).to receive(:exec).and_return(double(stdout: content)) expect(Beaker::Command).to receive(:new).with('powershell.exe', array_including("-Command type #{path}")) expect(instance.cat(path)).to eq(content) end end describe '#file_exist?' do let(:path) { '/path/to/test/file.txt' } context 'file exists' do it 'returns true' do expect(instance).to receive(:exec).and_return(double(stdout: "true\n")) expect(Beaker::Command).to receive(:new).with("if exist #{path} echo true") expect(instance.file_exist?(path)).to eq(true) end end context 'file does not exist' do it 'returns false' do expect(instance).to receive(:exec).and_return(double(stdout: "")) expect(Beaker::Command).to receive(:new).with("if exist #{path} echo true") expect(instance.file_exist?(path)).to eq(false) end end end describe '#tmpdir' do let(:tmp_path) { 'C:\\tmpdir\\' } let(:fake_command) { Beaker::Command.new('command1') } before do allow(instance).to receive(:execute).with(anything) end context 'with dirname sent' do let(:name) { 'my_dir' } it 'returns the path to my_dir' do expect(Beaker::Command).to receive(:new). with('powershell.exe', array_including('-Command [System.IO.Path]::GetTempPath()')). and_return(fake_command) expect(instance).to receive(:exec).with(instance_of(Beaker::Command)).and_return(double(stdout: tmp_path)) expect(Beaker::Command).to receive(:new). with('powershell.exe', array_including("-Command New-Item -Path '#{tmp_path}' -Force -Name '#{name}' -ItemType 'directory'")). and_return(fake_command) expect(instance).to receive(:exec).with(instance_of(Beaker::Command)).and_return(true) expect(instance.tmpdir(name)).to eq(File.join(tmp_path, name)) end end context 'without dirname sent' do let(:name) { '' } let(:random_dir) { 'dirname' } it 'returns the path to random name dir' do expect(Beaker::Command).to receive(:new). with('powershell.exe', array_including('-Command [System.IO.Path]::GetTempPath()')). and_return(fake_command) expect(instance).to receive(:exec).with(instance_of(Beaker::Command)).and_return(double(stdout: tmp_path)) expect(Beaker::Command).to receive(:new). with('powershell.exe', array_including('-Command [System.IO.Path]::GetRandomFileName()')). and_return(fake_command) expect(instance).to receive(:exec).with(instance_of(Beaker::Command)).and_return(double(stdout: random_dir)) expect(Beaker::Command).to receive(:new). with('powershell.exe', array_including("-Command New-Item -Path '#{tmp_path}' -Force -Name '#{random_dir}' -ItemType 'directory'")). and_return(fake_command) expect(instance).to receive(:exec).with(instance_of(Beaker::Command)).and_return(true) expect(instance.tmpdir).to eq(File.join(tmp_path, random_dir)) end end end end end beaker-4.30.0/spec/beaker/host/pswindows/user_spec.rb000066400000000000000000000020201407603575700225510ustar00rootroot00000000000000require 'spec_helper' class PSWindowsUserTest include PSWindows::User end describe PSWindowsUserTest do let( :wmic_output ) do <<-EOS Name=Administrator Name=bob foo Name=bob-dash Name=bob.foo Name=cyg_server EOS end let( :command ) { 'cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value' } let( :host ) { double.as_null_object } let( :result ) { Beaker::Result.new( host, command ) } describe '#user_list' do it 'returns user names list correctly' do result.stdout = wmic_output expect( subject ).to receive( :execute ).with( command ).and_yield(result) expect( subject.user_list ).to be === ['Administrator', 'bob foo', 'bob-dash', 'bob.foo', 'cyg_server'] end it 'yields correctly with the result object' do result.stdout = wmic_output expect( subject ).to receive( :execute ).and_yield(result) subject.user_list { |result| expect( result.stdout ).to be === wmic_output } end end end beaker-4.30.0/spec/beaker/host/pswindows_spec.rb000066400000000000000000000021631407603575700216030ustar00rootroot00000000000000require 'spec_helper' module PSWindows describe Host do let(:options) { @options ? @options : {} } let(:platform) { if @platform { :platform => Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'windows-vers-arch-extra' ) } end } let(:host) { opts = options.merge(platform) opts.merge!({ :is_cygwin => false }) make_host( 'name', opts ) } describe '#external_copy_base' do it 'returns previously calculated value if set' do external_copy_base_before = host.instance_variable_get( :@external_copy_base ) test_value = :testn8391 host.instance_variable_set( :@external_copy_base, test_value ) expect( host ).not_to receive( :execute ) expect( host.external_copy_base ).to be === test_value host.instance_variable_set( :@external_copy_base, external_copy_base_before ) end it 'calls the correct command if unset' do expect( host ).to receive( :execute ).with( /^for\ .*ALLUSERSPROFILE.*\%\~I$/ ) host.external_copy_base end end end end beaker-4.30.0/spec/beaker/host/unix/000077500000000000000000000000001407603575700171705ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/host/unix/exec_spec.rb000066400000000000000000000473321407603575700214640ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Unix::Exec do class UnixExecTest include Unix::Exec def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { UnixExecTest.new(opts, logger) } context "rm" do it "deletes" do path = '/path/to/delete' expect( instance ).to receive(:execute).with("rm -rf #{path}").and_return(0) expect( instance.rm_rf(path) ).to be === 0 end end context 'mv' do let(:origin) { '/origin/path/of/content' } let(:destination) { '/destination/path/of/content' } it 'rm first' do expect( instance ).to receive(:execute).with("rm -rf #{destination}").and_return(0) expect( instance ).to receive(:execute).with("mv #{origin} #{destination}").and_return(0) expect( instance.mv(origin, destination) ).to be === 0 end it 'does not rm' do expect( instance ).to receive(:execute).with("mv #{origin} #{destination}").and_return(0) expect( instance.mv(origin, destination, false) ).to be === 0 end end describe '#modified_at' do it 'calls execute with touch and timestamp' do time = '190101010000' path = '/path/to/file' expect( instance ).to receive(:execute).with("/bin/touch -mt #{time} #{path}").and_return(0) instance.modified_at(path, time) end end describe '#environment_string' do let(:host) { {'pathseparator' => ':'} } it 'returns a blank string if theres no env' do expect( instance ).to receive( :is_powershell? ).never expect( instance.environment_string( {} ) ).to be == '' end it 'takes an env hash with var_name/value pairs' do expect( instance.environment_string( {:HOME => '/', :http_proxy => 'http://foo'} ) ). to be == 'env HOME="/" http_proxy="http://foo" HTTP_PROXY="http://foo"' end it 'takes an env hash with var_name/value[Array] pairs' do expect( instance.environment_string( {:LD_PATH => ['/', '/tmp']}) ). to be == "env LD_PATH=\"/:/tmp\"" end end describe '#ssh_permit_user_environment' do context 'When called without error' do let (:directory) {'/directory'} let (:ssh_command) {"echo 'PermitUserEnvironment yes' | cat - /etc/ssh/sshd_config > #{directory}/sshd_config.permit"} let (:ssh_move) {"mv #{directory}/sshd_config.permit /etc/ssh/sshd_config"} platforms = PlatformHelpers::SYSTEMDPLATFORMS + PlatformHelpers::DEBIANPLATFORMS + PlatformHelpers::SYSTEMVPLATFORMS platforms.each do |platform| it "calls the correct commands for #{platform}" do opts['platform'] = platform expect(instance).to receive(:exec).twice expect(instance).to receive(:tmpdir).and_return(directory) expect(Beaker::Command).to receive(:new).with(ssh_move) expect(Beaker::Command).to receive(:new).with(ssh_command) expect(instance).to receive(:ssh_service_restart) expect{instance.ssh_permit_user_environment}.to_not raise_error end end end it 'raises an error on unsupported platforms' do opts['platform'] = 'notarealthing01-parts-arch' expect { instance.ssh_permit_user_environment }.to raise_error( ArgumentError, /#{opts['platform']}/ ) end end describe '#ssh_service_restart' do PlatformHelpers::SYSTEMDPLATFORMS.each do |platform| it "calls the correct command for #{platform}" do opts['platform'] = platform expect(instance).to receive(:exec) expect(Beaker::Command).to receive(:new).with("systemctl restart sshd.service") expect{instance.ssh_service_restart}.to_not raise_error end end PlatformHelpers::DEBIANPLATFORMS.each do |platform| it "calls the correct command for #{platform}" do opts['platform'] = platform expect(instance).to receive(:exec) expect(Beaker::Command).to receive(:new).with("service ssh restart") expect{instance.ssh_service_restart}.to_not raise_error end end PlatformHelpers::SYSTEMVPLATFORMS.each do |platform| it "calls the correct command for #{platform}" do opts['platform'] = "#{platform}-arch" expect(instance).to receive(:exec) expect(Beaker::Command).to receive(:new).with("/sbin/service sshd restart") expect{instance.ssh_service_restart}.to_not raise_error end end it 'raises an error on unsupported platforms' do opts['platform'] = 'notarealthing02-parts-arch' expect { instance.ssh_service_restart }.to raise_error( ArgumentError, /#{opts['platform']}/ ) end end describe '#prepend_commands' do it 'returns the pc parameter unchanged for non-cisco platforms' do allow( instance ).to receive( :[] ).with( :platform ).and_return( 'notcisco' ) answer_prepend_commands = 'pc_param_unchanged_13579' answer_test = instance.prepend_commands( 'fake_cmd', answer_prepend_commands ) expect( answer_test ).to be === answer_prepend_commands end end describe '#selinux_enabled?' do it 'calls selinuxenabled and selinux is enabled' do expect(Beaker::Command).to receive(:new).with("sudo selinuxenabled").and_return(0) expect(instance).to receive(:exec).with(0, :accept_all_exit_codes => true).and_return(generate_result("test", {:exit_code => 0})) expect(instance.selinux_enabled?).to be === true end it 'calls selinuxenabled and selinux is not enabled' do expect(Beaker::Command).to receive(:new).with("sudo selinuxenabled").and_return(1) expect(instance).to receive(:exec).with(1, :accept_all_exit_codes => true).and_return(generate_result("test", {:exit_code => 1})) expect(instance.selinux_enabled?).to be === false end end describe '#reboot' do year = Time.now.strftime('%Y') check_cmd_output = { :centos6 => { :who => { :initial => " system boot #{year}-05-13 03:51", :success => " system boot #{year}-05-13 03:52", }, :last => { :initial => <<~LAST_F, reboot system boot 2.6.32-754.29.1. Tue May 5 17:34:52 #{year} - Tue May 5 17:52:48 #{year} (00:17) reboot system boot 2.6.32-754.29.1. Mon May 4 18:45:43 #{year} - Mon May 5 05:35:44 #{year} (4+01:50) LAST_F :success => <<~LAST_F, reboot system boot 2.6.32-754.29.1. Tue May 5 17:52:48 #{year} - Tue May 5 17:52:49 #{year} (00:17) reboot system boot 2.6.32-754.29.1. Mon May 4 18:45:43 #{year} - Mon May 5 05:35:44 #{year} (4+01:50) LAST_F }, }, :centos7 => { :who => { :initial => " system boot #{year}-05-13 03:51", :success => " system boot #{year}-05-13 03:52", }, :last => { :initial => <<~LAST_F, reboot system boot 3.10.0-1127.el7. Tue May 5 17:34:52 #{year} - Tue May 5 17:52:48 #{year} (00:17) reboot system boot 3.10.0-1127.el7. Mon May 4 18:45:43 #{year} - Mon May 5 05:35:44 #{year} (4+01:50) LAST_F :success => <<~LAST_F, reboot system boot 3.10.0-1127.el7. Tue May 5 17:52:48 #{year} - Tue May 5 17:52:49 #{year} (00:17) reboot system boot 3.10.0-1127.el7. Mon May 4 18:45:43 #{year} - Mon May 5 05:35:44 #{year} (4+01:50) LAST_F }, }, :centos8 => { :who => { :initial => " system boot #{year}-05-13 03:51", :success => " system boot #{year}-05-13 03:52", }, :last => { :initial => <<~LAST_F, reboot system boot 4.18.0-147.8.1.e Tue May 5 17:34:52 #{year} still running reboot system boot 4.18.0-147.8.1.e Mon May 4 17:41:27 #{year} - Tue May 5 17:00:00 #{year} (5+00:11) LAST_F :success => <<~LAST_F, reboot system boot 4.18.0-147.8.1.e Tue May 5 17:34:53 #{year} still running reboot system boot 4.18.0-147.8.1.e Mon May 4 17:41:27 #{year} - Tue May 5 17:00:00 #{year} (5+00:11) LAST_F }, }, :freebsd => { # last -F doesn't work on freebsd so no output will be returned :who => { :initial => ' system boot May 13 03:51', :success => ' system boot May 13 03:52', } }, } # no-op response let (:response) { double( 'response' ) } let (:boot_time_initial_response) { double( 'response' ) } let (:boot_time_success_response) { double( 'response' ) } let (:sleep_time) { 10 } before :each do # stubs enough to survive the first boot_time call & output parsing # note: just stubs input-chain between calls, parsing methods still run allow(Beaker::Command).to receive(:new).with('last -F reboot || who -b').and_return(:boot_time_command_stub) allow(boot_time_initial_response).to receive(:stdout).and_return(boot_time_initial_stdout) allow(boot_time_success_response).to receive(:stdout).and_return(boot_time_success_stdout) allow(instance).to receive(:sleep) allow(Beaker::Command).to receive(:new).with("/bin/systemctl reboot -i || reboot || /sbin/shutdown -r now").and_return(:shutdown_command_stub) end context 'new boot time greater than old boot time' do check_cmd_output.each do |check_os, cmd_opts| cmd_opts.each do |cmd_name, cmd_outputs| context "on '#{check_os}' with the '#{cmd_name}' command" do let (:boot_time_initial_stdout) { cmd_outputs[:initial] } let (:boot_time_success_stdout) { cmd_outputs[:success] } it 'passes with defaults' do expect(instance).to receive(:sleep).with(sleep_time) # bypass shutdown command itself expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response) expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).once # allow the second boot_time and the hash arguments in exec expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_success_response).once expect(instance.reboot).to be(nil) end it 'passes with wait_time_parameter' do expect(instance).to receive(:sleep).with(10) # bypass shutdown command itself expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).once # allow the second boot_time and the hash arguments in exec expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_success_response).once expect(instance.reboot(10)).to be(nil) end it 'passes with max_connection_tries parameter' do expect(instance).to receive(:sleep).with(sleep_time) # bypass shutdown command itself expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).once # allow the second boot_time and the hash arguments in exec expect(instance).to receive( :exec ).with(:boot_time_command_stub, hash_including(:max_connection_tries => 20)).and_return(boot_time_success_response).once expect(instance.reboot(sleep_time, 20)).to be(nil) end context 'command errors' do before :each do allow(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).at_least(:once) end it 'raises a reboot failure when command fails' do expect(instance).to receive(:sleep).at_least(:once) expect(instance).to receive(:exec).with(:shutdown_command_stub, anything).and_raise(Host::CommandFailure).at_least(:once) expect{ instance.reboot }.to raise_error(Beaker::Host::CommandFailure) end it 'raises a reboot failure when we receive an unexpected error' do expect(instance).to receive(:sleep).at_least(:once) expect(instance).to receive(:exec).with(:shutdown_command_stub, anything).and_raise(Net::SSH::HostKeyError).at_least(:once) expect { instance.reboot }.to raise_error(Net::SSH::HostKeyError) end context 'incorrect time string' do context 'original time' do let (:boot_time_initial_stdout) { 'boot bad' } it 'raises a reboot failure' do # Handle the 'retry' allow(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).at_least(:once) expect(instance).not_to receive(:sleep) expect { instance.reboot }.to raise_error(Beaker::Host::RebootWarning, /Found no valid times in .*/) end end context 'current time' do let (:boot_time_success_stdout) { 'boot bad' } it 'raises a reboot failure' do expect(instance).to receive(:exec).with(:shutdown_command_stub, anything).and_return(response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).once # allow the second boot_time and the hash arguments in exec, repeated 10 times by default expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_success_response).at_least(:once) expect { instance.reboot(10,9,1) }.to raise_error(Beaker::Host::RebootWarning, /Found no valid times in .*/) end end end end end end end end context 'system did not reboot' do check_cmd_output.each do |check_os, cmd_opts| cmd_opts.each do |cmd_name, cmd_outputs| context "on '#{check_os}' with the '#{cmd_name}' command" do let (:boot_time_initial_stdout) { cmd_outputs[:initial] } let (:boot_time_success_stdout) { cmd_outputs[:initial] } it 'raises RebootFailure' do expect(instance).to receive(:sleep).with(sleep_time) # bypass shutdown command itself expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_success_response).once expect { instance.reboot }.to raise_error(Beaker::Host::RebootFailure, /Boot time did not reset/) end it 'raises RebootFailure if the number of retries is changed' do expect(instance).to receive(:sleep).with(sleep_time) # bypass shutdown command itself expect(instance).to receive( :exec ).with(:shutdown_command_stub, anything).and_return(response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_initial_response).once expect(instance).to receive( :exec ).with(:boot_time_command_stub, anything).and_return(boot_time_success_response).once expect { instance.reboot(wait_time=sleep_time, max_connection_tries=9, boot_time_retries=10) }.to raise_error(Beaker::Host::RebootFailure, /Boot time did not reset/) end end end end end end describe '#enable_remote_rsyslog' do it 'always calls restart' do opts['platform'] = 'ubuntu-18-x86_64' allow(Beaker::Command).to receive(:new).with(anything) allow(instance).to receive(:exec) expect(Beaker::Command).to receive(:new).with("systemctl restart rsyslog") instance.enable_remote_rsyslog end end describe '#which' do context 'when type -P works' do before do expect(instance).to receive(:execute) .with('type -P true', :accept_all_exit_codes => true).and_return('/bin/true').once allow(instance).to receive(:execute) .with(where_command, :accept_all_exit_codes => true).and_return(result) end context 'when only the environment variable PATH is used' do let(:where_command) { "type -P ruby" } let(:result) { "/usr/bin/ruby.exe" } it 'returns the correct path' do response = instance.which('ruby') expect(response).to eq(result) end end context 'when command is not found' do let(:where_command) { "type -P unknown" } let(:result) { '' } it 'return empty string if command is not found' do response = instance.which('unknown') expect(response).to eq(result) end end end context 'when which works' do before do expect(instance).to receive(:execute) .with('type -P true', :accept_all_exit_codes => true).and_return('').once expect(instance).to receive(:execute) .with('which true', :accept_all_exit_codes => true).and_return('/bin/true').once allow(instance).to receive(:execute) .with(where_command, :accept_all_exit_codes => true).and_return(result) end context 'when only the environment variable PATH is used' do let(:where_command) { "which ruby" } let(:result) { "/usr/bin/ruby.exe" } it 'returns the correct path' do response = instance.which('ruby') expect(response).to eq(result) end end context 'when command is not found' do let(:where_command) { "which unknown" } let(:result) { '' } it 'return empty string if command is not found' do response = instance.which('unknown') expect(response).to eq(result) end end end context 'when neither works' do before do expect(instance).to receive(:execute) .with('type -P true', :accept_all_exit_codes => true).and_return('').once expect(instance).to receive(:execute) .with('which true', :accept_all_exit_codes => true).and_return('').once end context 'when only the environment variable PATH is used' do it 'fails correctly' do expect{instance.which('ruby')}.to raise_error(/suitable/) end end end end end end beaker-4.30.0/spec/beaker/host/unix/file_spec.rb000066400000000000000000000210131407603575700214430ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Unix::File do class UnixFileTest include Unix::File def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end def logger @logger end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:platform) { if @platform { 'platform' => Beaker::Platform.new( @platform) } else { 'platform' => Beaker::Platform.new( 'osx-10.9-x86_64' ) } end } let (:instance) { UnixFileTest.new(opts.merge(platform), logger) } describe '#repo_type' do ['centos','redhat'].each do |platform| it "returns correctly for platform '#{platform}'" do @platform = "#{platform}-5-x86_64" expect( instance.repo_type ).to be === 'rpm' end end it 'returns correctly for debian-based platforms' do @platform = 'debian-6-x86_64' expect( instance.repo_type ).to be === 'deb' end it 'errors for all other platform types' do @platform = 'eos-4-x86_64' expect { instance.repo_type }.to raise_error( ArgumentError, /repo\ type\ not\ known/ ) end end describe '#package_config_dir' do ['centos','redhat'].each do |platform| it "returns correctly for platform '#{platform}'" do @platform = "#{platform}-5-x86_64" expect( instance.package_config_dir ).to be === '/etc/yum.repos.d/' end end it 'returns correctly for debian-based platforms' do @platform = 'debian-6-x86_64' expect( instance.package_config_dir ).to be === '/etc/apt/sources.list.d' end it 'returns correctly for sles-based platforms' do @platform = 'sles-12-x86_64' expect( instance.package_config_dir ).to be === '/etc/zypp/repos.d/' end it 'returns correctly for opensuse-based platforms' do @platform = 'opensuse-15-x86_64' expect( instance.package_config_dir ).to be === '/etc/zypp/repos.d/' end it 'errors for all other platform types' do @platform = 'eos-4-x86_64' expect { instance.package_config_dir }.to raise_error( ArgumentError, /package\ config\ dir\ unknown/ ) end end describe '#repo_filename' do ['centos','redhat'].each do |platform| it "sets the el portion correctly for '#{platform}'" do @platform = "#{platform}-5-x86_64" allow( instance ).to receive( :is_pe? ) { false } filename = instance.repo_filename( 'pkg_name', 'pkg_version7' ) expect( filename ).to match( /sion7\-el\-/ ) end end it 'sets the sles portion correctly for sles platforms' do @platform = 'sles-11-x86_64' allow( instance ).to receive( :is_pe? ) { false } filename = instance.repo_filename( 'pkg_name', 'pkg_version7' ) expect( filename ).to match( /sion7\-sles\-/ ) end it 'sets the opensuse portion correctly for opensuse platforms' do @platform = 'opensuse-15-x86_64' allow( instance ).to receive( :is_pe? ) { false } filename = instance.repo_filename( 'pkg_name', 'pkg_version7' ) expect( filename ).to match( /sion7\-opensuse\-/ ) end it 'builds the filename correctly for el-based platforms' do @platform = 'el-21-x86_64' allow( instance ).to receive( :is_pe? ) { false } filename = instance.repo_filename( 'pkg_name', 'pkg_version8' ) correct = 'pl-pkg_name-pkg_version8-el-21-x86_64.repo' expect( filename ).to be === correct end it 'builds the filename correctly for redhatfips platforms' do @platform = 'el-7-x86_64' allow(instance).to receive(:[]).with('platform') { platform['platform'] } expect(instance).to receive(:[]).with('packaging_platform') { 'redhatfips-7-x86_64' } filename = instance.repo_filename('pkg_name', 'pkg_version') correct = 'pl-pkg_name-pkg_version-redhatfips-7-x86_64.repo' expect( filename ).to be === correct end it 'adds in the PE portion of the filename correctly for el-based PE hosts' do @platform = 'el-21-x86_64' allow( instance ).to receive( :is_pe? ) { true } filename = instance.repo_filename( 'pkg_name', 'pkg_version9' ) correct = 'pl-pkg_name-pkg_version9-el-21-x86_64.repo' expect( filename ).to be === correct end it 'builds the filename correctly for debian-based platforms' do @platform = 'debian-8-x86_64' filename = instance.repo_filename( 'pkg_name', 'pkg_version10' ) correct = 'pl-pkg_name-pkg_version10-jessie.list' expect( filename ).to be === correct end it 'uses the variant for the codename on the cumulus platform' do @platform = 'cumulus-2.5-x86_64' filename = instance.repo_filename( 'pkg_name', 'pkg_version11' ) correct = 'pl-pkg_name-pkg_version11-cumulus.list' expect( filename ).to be === correct end it 'adds wrlinux to variant on cisco platforms' do @platform = 'cisco_nexus-7-x86_64' allow( instance ).to receive( :is_pe? ) { false } filename = instance.repo_filename( 'pkg_name', 'pkg_version12' ) expect( filename ).to match( /sion12\-cisco\-wrlinux\-/ ) end it 'errors for non-el or debian-based platforms' do @platform = 'freebsd-22-x86_64' expect { instance.repo_filename( 'pkg_name', 'pkg_version' ) }. to raise_error( ArgumentError, /repo\ filename\ pattern\ not\ known/ ) end end describe '#noask_file_text' do it 'errors on non-solaris platforms' do @platform = 'cumulus-4000-x86_64' expect { instance.noask_file_text }.to raise_error( ArgumentError, /^noask\ file\ text\ unknown/ ) end it 'errors on solaris versions other than 10' do @platform = 'solaris-11-x86_64' expect { instance.noask_file_text }.to raise_error( ArgumentError, /^noask\ file\ text\ unknown/ ) end it 'returns the noask file correctly for solaris 10' do @platform = 'solaris-10-x86_64' text = instance.noask_file_text expect( text ).to match( /instance\=overwrite/ ) expect( text ).to match( /space\=quit/ ) expect( text ).to match( /basedir\=default/ ) end end describe '#chown' do let (:user) { 'someuser' } let (:path) { '/path/to/chown/on' } it 'calls the system method' do expect( instance ).to receive( :execute ).with( "chown #{user} #{path}" ).and_return( 0 ) expect( instance.chown( user, path ) ).to be === 0 end it 'passes -R if recursive' do expect( instance ).to receive( :execute ).with( "chown \-R #{user} #{path}" ) instance.chown( user, path, true ) end end describe '#cat' do let (:path) { '/path/to/cat/on' } it 'calls cat for path' do expect( instance ).to receive( :execute ).with( "cat #{path}" ).and_return( 0 ) expect( instance.cat( path ) ).to be === 0 end end describe '#chmod' do context 'not recursive' do it 'calls execute with chmod' do path = '/path/to/file' mod = '+x' expect( instance ).to receive(:execute).with("chmod #{mod} #{path}") instance.chmod(mod, path) end end context 'recursive' do it 'calls execute with chmod' do path = '/path/to/file' mod = '+x' expect( instance ).to receive(:execute).with("chmod -R #{mod} #{path}") instance.chmod(mod, path, true) end end end describe '#chgrp' do let (:group) { 'somegroup' } let (:path) { '/path/to/chgrp/on' } it 'calls the system method' do expect( instance ).to receive( :execute ).with( "chgrp #{group} #{path}" ).and_return( 0 ) expect( instance.chgrp( group, path ) ).to be === 0 end it 'passes -R if recursive' do expect( instance ).to receive( :execute ).with( "chgrp \-R #{group} #{path}" ) instance.chgrp( group, path, true ) end end describe '#ls_ld' do let (:path) { '/path/to/ls_ld' } it 'calls the system method' do expect( instance ).to receive( :execute ).with( "ls -ld #{path}" ).and_return( 0 ) expect( instance.ls_ld( path ) ).to be === 0 end end end end beaker-4.30.0/spec/beaker/host/unix/pkg_spec.rb000066400000000000000000000727541407603575700213270ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Unix::Pkg do class UnixPkgTest include Unix::Pkg def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def []=(k, v) @hash[k] = v end def to_s "me" end def exec #noop end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { UnixPkgTest.new(opts, logger) } context 'Package deployment tests' do path = '/some/file/path' name = 'package_name' version = '1.0.0' describe '#deploy_package_repo' do it 'returns a warning if there is no file at the path specified' do expect(logger).to receive(:warn) allow(File).to receive(:exists?).with(path).and_return(false) instance.deploy_package_repo(path,name,version) end it 'calls #deploy_apt_repo for huaweios systems' do @opts = {'platform' => 'huaweios-is-me'} expect(instance).to receive(:deploy_apt_repo) allow(File).to receive(:exists?).with(path).and_return(true) instance.deploy_package_repo(path,name,version) end it 'calls #deploy_apt_repo for debian systems' do @opts = {'platform' => 'ubuntu-is-me'} expect(instance).to receive(:deploy_apt_repo) allow(File).to receive(:exists?).with(path).and_return(true) instance.deploy_package_repo(path,name,version) end it 'calls #deploy_yum_repo for el systems' do @opts = {'platform' => 'el-is-me'} expect(instance).to receive(:deploy_yum_repo) allow(File).to receive(:exists?).with(path).and_return(true) instance.deploy_package_repo(path,name,version) end it 'calls #deploy_zyp_repo for sles systems' do @opts = {'platform' => 'sles-is-me'} expect(instance).to receive(:deploy_zyp_repo) allow(File).to receive(:exists?).with(path).and_return(true) instance.deploy_package_repo(path,name,version) end it 'calls #deploy_zyp_repo for opensuse systems' do @opts = {'platform' => 'opensuse-is-me'} expect(instance).to receive(:deploy_zyp_repo) allow(File).to receive(:exists?).with(path).and_return(true) instance.deploy_package_repo(path,name,version) end it 'raises an error for unsupported systems' do @opts = {'platform' => 'windows-is-me'} allow(File).to receive(:exists?).with(path).and_return(true) expect{instance.deploy_package_repo(path,name,version)}.to raise_error(RuntimeError) end end describe '#deploy_apt_repo' do it 'warns and exits when no codename exists for the debian platform' do @opts = {'platform' => 'ubuntu-is-me'} expect(logger).to receive(:warn) allow(@opts['platform']).to receive(:codename).and_return(nil) expect(instance).to receive(:deploy_apt_repo).and_return(instance.deploy_apt_repo(path,name,version)) allow(File).to receive(:exists?).with(path).and_return(true) instance.deploy_package_repo(path,name,version) end end end context "check_for_package" do it "checks correctly on sles" do @opts = {'platform' => 'sles-is-me'} pkg = 'sles_package' expect( Beaker::Command ).to receive( :new ).with( /^rpmkeys.*nightlies.puppetlabs.com.*/, anything, anything ).and_return('').ordered.once expect( Beaker::Command ).to receive(:new).with("zypper --gpg-auto-import-keys se -i --match-exact #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('').ordered.once expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})).exactly(2).times expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on opensuse" do @opts = {'platform' => 'opensuse-is-me'} pkg = 'sles_package' expect( Beaker::Command ).to receive( :new ).with( /^rpmkeys.*nightlies.puppetlabs.com.*/, anything, anything ).and_return('').ordered.once expect( Beaker::Command ).to receive(:new).with("zypper --gpg-auto-import-keys se -i --match-exact #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('').ordered.once expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})).exactly(2).times expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on fedora" do @opts = {'platform' => 'fedora-is-me'} pkg = 'fedora_package' expect( Beaker::Command ).to receive(:new).with("rpm -q #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end ['centos','redhat'].each do |platform| it "checks correctly on #{platform}" do @opts = {'platform' => "#{platform}-is-me"} pkg = "#{platform}_package" expect( Beaker::Command ).to receive(:new).with("rpm -q #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end end it "checks correctly on EOS" do @opts = {'platform' => 'eos-is-me'} pkg = 'eos-package' expect( Beaker::Command ).to receive(:new).with("rpm -q #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on el-" do @opts = {'platform' => 'el-is-me'} pkg = 'el_package' expect( Beaker::Command ).to receive(:new).with("rpm -q #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on huaweios" do @opts = {'platform' => 'huaweios-is-me'} pkg = 'debian_package' expect( Beaker::Command ).to receive(:new).with("dpkg -s #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on debian" do @opts = {'platform' => 'debian-is-me'} pkg = 'debian_package' expect( Beaker::Command ).to receive(:new).with("dpkg -s #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on ubuntu" do @opts = {'platform' => 'ubuntu-is-me'} pkg = 'ubuntu_package' expect( Beaker::Command ).to receive(:new).with("dpkg -s #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on cumulus" do @opts = {'platform' => 'cumulus-is-me'} pkg = 'cumulus_package' expect( Beaker::Command ).to receive(:new).with("dpkg -s #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on solaris-11" do @opts = {'platform' => 'solaris-11-is-me'} pkg = 'solaris-11_package' expect( Beaker::Command ).to receive(:new).with("pkg info #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on solaris-10" do @opts = {'platform' => 'solaris-10-is-me'} pkg = 'solaris-10_package' expect( Beaker::Command ).to receive(:new).with("pkginfo #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "checks correctly on archlinux" do @opts = {'platform' => 'archlinux-is-me'} pkg = 'archlinux_package' expect( Beaker::Command ).to receive(:new).with("pacman -Q #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', :accept_all_exit_codes => true).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.check_for_package(pkg) ).to be === true end it "returns false for el-4" do @opts = {'platform' => 'el-4-is-me'} pkg = 'el-4_package' expect( instance.check_for_package(pkg) ).to be === false end it "raises on unknown platform" do @opts = {'platform' => 'nope-is-me'} pkg = 'nope_package' expect{ instance.check_for_package(pkg) }.to raise_error end end describe '#update_apt_if_needed' do PlatformHelpers::DEBIANPLATFORMS.each do |platform| it "calls update for #{platform}" do @opts = {'platform' => platform} instance.instance_variable_set("@apt_needs_update", true) expect(instance).to receive('execute').with("apt-get update") expect{instance.update_apt_if_needed}.to_not raise_error end end end context "install_package" do PlatformHelpers::DEBIANPLATFORMS.each do |platform| it "uses apt-get for #{platform}" do @opts = {'platform' => platform} pkg = 'pkg' expect( Beaker::Command ).to receive(:new).with("apt-get install --force-yes -y #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.install_package(pkg) ).to be == "hello" end end (1..21).to_a.each do | fedora_release | it "uses yum on fedora-#{fedora_release}" do @opts = {'platform' => "fedora-#{fedora_release}-is-me"} pkg = 'fedora_package' expect( Beaker::Command ).to receive(:new).with("yum -y install #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.install_package(pkg) ).to be == "hello" end end (22..39).to_a.each do | fedora_release | it "uses dnf on fedora-#{fedora_release}" do @opts = {'platform' => "fedora-#{fedora_release}-is-me"} pkg = 'fedora_package' expect( Beaker::Command ).to receive(:new).with("dnf -y install #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.install_package(pkg) ).to be == "hello" end end it "uses pacman on archlinux" do @opts = {'platform' => 'archlinux-is-me'} pkg = 'archlinux_package' expect( Beaker::Command ).to receive(:new).with("pacman -S --noconfirm #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.install_package(pkg) ).to be == "hello" end end describe '#uninstall_package' do PlatformHelpers::DEBIANPLATFORMS.each do |platform| it "calls pkg uninstall for #{platform}" do @opts = {'platform' => platform} expect( Beaker::Command ).to receive(:new).with("apt-get purge -y pkg", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect(instance.uninstall_package('pkg')).to be == "hello" end (1..21).to_a.each do | fedora_release | it "uses yum on fedora-#{fedora_release}" do @opts = {'platform' => "fedora-#{fedora_release}-is-me"} pkg = 'fedora_package' expect( Beaker::Command ).to receive(:new).with("yum -y remove #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.uninstall_package(pkg) ).to be == "hello" end end (22..39).to_a.each do | fedora_release | it "uses dnf on fedora-#{fedora_release}" do @opts = {'platform' => "fedora-#{fedora_release}-is-me"} pkg = 'fedora_package' expect( Beaker::Command ).to receive(:new).with("dnf -y remove #{pkg}", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.uninstall_package(pkg) ).to be == "hello" end end end end describe '#puppet_agent_dev_package_info' do let(:download_opts) {{download_url: 'http://trust.me'}} # These platforms are consistent across puppet collections shared_examples 'consistent platforms' do |puppet_collection, puppet_agent_version| platforms = { 'solaris-10-x86_64' => ["solaris/10/#{puppet_collection}", "puppet-agent-#{puppet_agent_version}-1.i386.pkg.gz"], 'solaris-11-x86_64' => ["solaris/11/#{puppet_collection}", "puppet-agent@#{puppet_agent_version},5.11-1.i386.p5p"], 'sles-11-x86_64' => ["sles/11/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.sles11.x86_64.rpm"], 'opensuse-15-x86_64' => ["sles/15/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.sles15.x86_64.rpm"], 'aix-6.1-power' => ["aix/6.1/#{puppet_collection}/ppc", "puppet-agent-#{puppet_agent_version}-1.aix6.1.ppc.rpm"], 'el-7-x86_64' => ["el/7/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el7.x86_64.rpm"], 'centos-7-x86_64' => ["el/7/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el7.x86_64.rpm"], 'oracle-7-x86_64' => ["el/7/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el7.x86_64.rpm"], 'redhat-7-x86_64' => ["el/7/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el7.x86_64.rpm"], 'scientific-7-x86_64' => ["el/7/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el7.x86_64.rpm"], 'el-8-x86_64' => ["el/8/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el8.x86_64.rpm"], 'centos-8-x86_64' => ["el/8/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el8.x86_64.rpm"], 'oracle-8-x86_64' => ["el/8/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el8.x86_64.rpm"], 'redhat-8-x86_64' => ["el/8/#{puppet_collection}/x86_64", "puppet-agent-#{puppet_agent_version}-1.el8.x86_64.rpm"], } platforms.each do |p, v| it "accomodates platform #{p} without erroring" do @opts = {'platform' => Beaker::Platform.new(p)} allow( instance ).to receive(:link_exists?).and_return(true) expect( instance.puppet_agent_dev_package_info(puppet_collection, puppet_agent_version, download_opts) ).to eq(v) end end end # AIX platform/package pairs differ accross collections shared_examples 'aix platform' do |package_version, platform_version, puppet_collection, puppet_agent_version| it "selects AIX #{package_version} packages for AIX #{platform_version}" do @opts = { 'platform' => Beaker::Platform.new("aix-#{platform_version}-power") } allow( instance ).to receive(:link_exists?).and_return(true) expect( instance.puppet_agent_dev_package_info(puppet_collection, puppet_agent_version, download_opts) ) .to eq(["aix/#{package_version}/#{puppet_collection}/ppc", "puppet-agent-#{puppet_agent_version}-1.aix#{package_version}.ppc.rpm"]) end end context 'with puppet-agent 1.y.z' do puppet_collection = 'PC1' puppet_agent_version = '1.2.3' include_examples 'consistent platforms', puppet_collection, puppet_agent_version include_examples 'aix platform', '5.3', '5.3', puppet_collection, puppet_agent_version include_examples 'aix platform', '7.1', '7.1', puppet_collection, puppet_agent_version include_examples 'aix platform', '7.1', '7.2', puppet_collection, puppet_agent_version end context 'with puppet-agent 5.y.z' do puppet_collection = 'puppet5' puppet_agent_version = '5.4.3' include_examples 'consistent platforms', puppet_collection, puppet_agent_version include_examples 'aix platform', '7.1', '7.1', puppet_collection, puppet_agent_version include_examples 'aix platform', '7.1', '7.2', puppet_collection, puppet_agent_version end context 'with puppet-agent 5.99.z' do puppet_collection = 'puppet6' puppet_agent_version = '5.99.0' include_examples 'consistent platforms', puppet_collection, puppet_agent_version include_examples 'aix platform', '6.1', '7.1', puppet_collection, puppet_agent_version include_examples 'aix platform', '6.1', '7.2', puppet_collection, puppet_agent_version end context 'with puppet6' do puppet_collection = 'puppet6' puppet_agent_version = '6.6.6' include_examples 'consistent platforms', puppet_collection, puppet_agent_version include_examples 'aix platform', '6.1', '7.1', puppet_collection, puppet_agent_version include_examples 'aix platform', '6.1', '7.2', puppet_collection, puppet_agent_version end end describe '#upgrade_package' do PlatformHelpers::DEBIANPLATFORMS.each do |platform| it "calls the correct apt-get incantation for #{platform}" do @opts = {'platform' => platform} expect( Beaker::Command ).to receive(:new).with("apt-get install -o Dpkg::Options::='--force-confold' -y --force-yes pkg", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect(instance.upgrade_package('pkg')).to be == "hello" end end end context "install_package_with_rpm" do it "accepts a package as a single argument" do @opts = {'platform' => 'el-is-me'} pkg = 'redhat_package' expect( Beaker::Command ).to receive(:new).with("rpm -Uvh #{pkg} ", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.install_package_with_rpm(pkg) ).to be == "hello" end it "accepts a package and additional options" do @opts = {'platform' => 'el-is-me'} pkg = 'redhat_package' cmdline_args = '--foo' expect( Beaker::Command ).to receive(:new).with("rpm #{cmdline_args} -Uvh #{pkg} ", [], {:prepend_cmds=>nil, :cmdexe=>false}).and_return('') expect( instance ).to receive(:exec).with('', {}).and_return(generate_result("hello", {:exit_code => 0})) expect( instance.install_package_with_rpm(pkg, cmdline_args) ).to be == "hello" end end context 'extract_rpm_proxy_options' do [ 'http://myproxy.com:3128/', 'https://myproxy.com:3128/', 'https://myproxy.com:3128', 'http://myproxy.com:3128', ].each do |url| it "correctly extracts rpm proxy options for #{url}" do expect( instance.extract_rpm_proxy_options(url) ).to be == '--httpproxy myproxy.com --httpport 3128' end end url = 'http:/myproxy.com:3128' it "fails to extract rpm proxy options for #{url}" do expect{ instance.extract_rpm_proxy_options(url) }.to raise_error(RuntimeError, /Cannot extract host and port/) end end context '#pe_puppet_agent_promoted_package_install' do context 'on solaris platforms' do before :each do allow( subject ).to receive( :fetch_http_file ) allow( subject ).to receive( :scp_to ) allow( subject ).to receive( :configure_type_defaults_on ) end context 'version support' do (7..17).each do |version| supported_version = version == 10 || version == 11 supported_str = ( supported_version ? '' : 'not ') test_title = "does #{supported_str}support version #{version}" it "#{test_title}" do solaris_platform = Beaker::Platform.new("solaris-#{version}-x86_64") @opts = {'platform' => solaris_platform} allow( instance ).to receive( :execute ) allow( instance ).to receive( :exec ) if supported_version if version == 10 allow( instance ).to receive( :noask_file_text ) allow( instance ).to receive( :create_remote_file ) allow( instance ).to receive( :execute ).with('/opt/csw/bin/pkgutil -y -i pkgutil') end # only expect diff in the last line: .not_to vs .to raise_error expect{ instance.pe_puppet_agent_promoted_package_install( 'oh_cp_base', 'oh_cp_dl', 'oh_cp_fl', 'dl_fl', {} ) }.not_to raise_error else expect{ instance.pe_puppet_agent_promoted_package_install( 'oh_cp_base', 'oh_cp_dl', 'oh_cp_fl', 'dl_fl', {} ) }.to raise_error(ArgumentError, /^Solaris #{version} is not supported/ ) end end end end context 'on solaris 10' do before :each do solaris_platform = Beaker::Platform.new('solaris-10-x86_64') @opts = {'platform' => solaris_platform} end it 'sets a noask file' do allow( instance ).to receive( :execute ) allow( instance ).to receive( :exec ) expect( instance ).to receive( :noask_file_text ) expect( instance ).to receive( :create_remote_file ) instance.pe_puppet_agent_promoted_package_install('', '', '', '', {}) end it 'calls the correct install command' do allow( instance ).to receive( :noask_file_text ) allow( instance ).to receive( :create_remote_file ) # a number of `execute` calls before the one we're looking for allow( instance ).to receive( :execute ) allow( instance ).to receive( :exec ) # actual gunzip call to expect( Beaker::Command ).to receive( :new ).with( /^gunzip\ \-c\ / ) instance.pe_puppet_agent_promoted_package_install( 'oh_cp_base', 'oh_cp_dl', 'oh_cp_fl', 'dl_fl', {} ) end end context 'on solaris 11' do before :each do solaris_platform = Beaker::Platform.new('solaris-11-x86_64') @opts = {'platform' => solaris_platform} end it 'calls the correct install command' do allow( instance ).to receive( :execute ) allow( instance ).to receive( :exec ) expect( Beaker::Command ).to receive( :new ).with( /^pkg\ install\ \-g / ) instance.pe_puppet_agent_promoted_package_install( 'oh_cp_base', 'oh_cp_dl', 'oh_cp_fl', 'dl_fl', {} ) end end end end describe '#install_local_package' do let( :platform ) { @platform || 'fedora' } let( :version ) { @version || 6 } before :each do allow( instance ).to receive( :[] ).with( 'platform' ) { Beaker::Platform.new("#{platform}-#{version}-x86_64") } end it 'Fedora 22-39: uses dnf' do (22...39).each do |version| @version = version package_file = 'test_123.yay' expect( instance ).to receive( :execute ).with( /^dnf.*#{package_file}$/ ) instance.install_local_package( package_file ) end end it 'Fedora 21 uses yum' do package_file = 'testing_456.yay' [21].each do |version| @version = version expect( instance ).to receive( :execute ).with( /^yum.*#{package_file}$/ ) instance.install_local_package( package_file ) end end it 'Centos & EL: uses yum' do package_file = 'testing_789.yay' ['centos','redhat'].each do |platform| @platform = platform expect( instance ).to receive( :execute ).with( /^yum.*#{package_file}$/ ) instance.install_local_package( package_file ) end end it 'Debian, Ubuntu, Cumulus: uses dpkg' do package_file = 'testing_012.yay' ['debian', 'ubuntu', 'cumulus'].each do |platform| @platform = platform expect( instance ).to receive( :execute ).with( /^dpkg.*#{package_file}$/ ) expect( instance ).to receive( :execute ).with( 'apt-get update' ) instance.install_local_package( package_file ) end end it 'Solaris: calls solaris-specific install method' do package_file = 'testing_345.yay' @platform = 'solaris' expect( instance ).to receive( :solaris_install_local_package ).with( package_file, anything ) instance.install_local_package( package_file ) end it 'OSX: calls host.install_package' do package_file = 'testing_678.yay' @platform = 'osx' expect( instance ).to receive( :install_package ).with( package_file ) instance.install_local_package( package_file ) end end describe '#uncompress_local_tarball' do let( :platform ) { @platform || 'fedora' } let( :version ) { @version || 6 } let( :tar_file ) { 'test.tar.gz' } let( :base_dir ) { '/base/dir/fake' } let( :download_file ) { 'download_file.txt' } before :each do allow( instance ).to receive( :[] ).with( 'platform' ) { Beaker::Platform.new("#{platform}-#{version}-x86_64") } end it 'rejects unsupported platforms' do @platform = 'cisco_nexus' expect { instance.uncompress_local_tarball( tar_file, base_dir, download_file ) }.to raise_error( /^Platform #{platform} .* not supported .* uncompress_local_tarball$/ ) end it 'untars the file given' do @platform = 'sles' expect( instance ).to receive( :execute ).with( /^tar .* #{tar_file} .* #{base_dir}$/ ) instance.uncompress_local_tarball( tar_file, base_dir, download_file ) end it 'untars the file given' do @platform = 'opensuse' expect( instance ).to receive( :execute ).with( /^tar .* #{tar_file} .* #{base_dir}$/ ) instance.uncompress_local_tarball( tar_file, base_dir, download_file ) end context 'on solaris' do before :each do @platform = 'solaris' end it 'rejects unsupported versions' do @version = '12' expect { instance.uncompress_local_tarball( tar_file, base_dir, download_file ) }.to raise_error( /^Solaris #{version} .* not supported .* uncompress_local_tarball$/ ) end it 'v10: gunzips before untaring' do @version = '10' expect( instance ).to receive( :execute ).with( /^gunzip #{tar_file}$/ ) expect( instance ).to receive( :execute ).with( /^tar .* #{download_file}$/ ) instance.uncompress_local_tarball( tar_file, base_dir, download_file ) end it 'v11: untars only' do @version = '11' expect( instance ).to receive( :execute ).with( /^tar .* #{tar_file}$/ ) instance.uncompress_local_tarball( tar_file, base_dir, download_file ) end end end end end beaker-4.30.0/spec/beaker/host/unix_spec.rb000066400000000000000000000220661407603575700205350ustar00rootroot00000000000000require 'spec_helper' module Unix describe Host do let( :options ) { @options ? @options : {} } let( :platform ) { if @platform { :platform => Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'el-vers-arch-extra' ) } end } let( :host ) { make_host( 'name', options.merge(platform) ) } let( :opts ) { { :download_url => 'download_url' } } describe '#solaris_puppet_agent_dev_package_info' do it 'raises an error if puppet_collection is not passed' do expect { host.solaris_puppet_agent_dev_package_info }.to raise_error( ArgumentError, /^Must\ provide\ puppet_collection/ ) end it 'raises an error if puppet_agent_version is not passed' do expect { host.solaris_puppet_agent_dev_package_info( 'collection' ) }.to raise_error( ArgumentError, /^Must\ provide\ puppet_agent_version/ ) end it 'raises an error if the download URL is not passed' do expect { host.solaris_puppet_agent_dev_package_info( 'collection', 'version' ) }.to raise_error( ArgumentError, /^Must\ provide\ opts\[\:download_url\]/ ) end it 'raises an error if called on a non-solaris platform' do @platform = 'ubuntu-14.04-x86_64' opts = { :download_url => 'download_url' } expect { host.solaris_puppet_agent_dev_package_info( 'collection', 'version', opts ) }.to raise_error( ArgumentError, /^Incorrect\ platform \'ubuntu\'/ ) end it 'sets release_path_end correctly' do @platform = 'solaris-10-arch' allow( host ).to receive( :link_exists? ) { true } release_path_end, _ = host.solaris_puppet_agent_dev_package_info( 'pa_collection4', 'pa_version', opts ) expect( release_path_end ).to be === "solaris/10/pa_collection4" end it 'sets the arch correctly for x86_64 platforms' do @platform = 'solaris-10-x86_64' allow( host ).to receive( :link_exists? ) { true } _, release_file = host.solaris_puppet_agent_dev_package_info( 'pa_collection', 'pa_version', opts ) expect( release_file ).to match( /i386/ ) expect( release_file ).not_to match( /x86_64/ ) end context 'sets release_file name appropriately for puppet-agent version' do context 'on solaris 10' do before :each do @platform = 'solaris-10-arch' end [ '1.0.1.786.477', '1.0.1.786.a477', '1.0.1.786.477-', '1.0.1.0000786.477', '1.000000.1.786.477', '-1.0.1.786.477', '1.2.5.38.6813', ].each do |pa_version| context "#{pa_version}" do it "URL exists" do allow( host ).to receive( :link_exists? ) { true } _, release_file = host.solaris_puppet_agent_dev_package_info( 'pa_collection', pa_version, opts ) expect( release_file ).to be === "puppet-agent-#{pa_version}-1.arch.pkg.gz" end it "fallback URL" do allow( host ).to receive( :link_exists? ) { false } _, release_file = host.solaris_puppet_agent_dev_package_info( 'pa_collection', pa_version, opts ) expect( release_file ).to be === "puppet-agent-#{pa_version}.arch.pkg.gz" end end end end context 'on solaris 11' do before :each do @platform = 'solaris-11-arch' end [ ['1.0.1.786.477', '1.0.1.786.477'], ['1.0.1.786.a477', '1.0.1.786.477'], ['1.0.1.786.477-', '1.0.1.786.477'], ['1.0.1.0000786.477', '1.0.1.786.477'], ['1.000000.1.786.477', '1.0.1.786.477'], ['-1.0.1.786.477', '1.0.1.786.477'], ['1.2.5-78-gbb3022f', '1.2.5.78.3022'], ['1.2.5.38.6813', '1.2.5.38.6813'] ].each do |pa_version, pa_version_cleaned| context "#{pa_version}" do it "URL exists" do allow( host ).to receive( :link_exists? ) { true } _, release_file = host.solaris_puppet_agent_dev_package_info( 'pa_collection', pa_version, opts ) expect( release_file ).to be === "puppet-agent@#{pa_version_cleaned},5.11-1.arch.p5p" end it "fallback URL" do allow( host ).to receive( :link_exists? ) { false } _, release_file = host.solaris_puppet_agent_dev_package_info( 'pa_collection', pa_version, opts ) expect( release_file ).to be === "puppet-agent@#{pa_version_cleaned},5.11.arch.p5p" end end end end end end describe '#puppet_agent_dev_package_info' do it 'raises an error if puppet_collection is not passed' do expect { host.puppet_agent_dev_package_info }.to raise_error( ArgumentError, /^Must\ provide\ puppet_collection/ ) end it 'raises an error if puppet_agent_version is not passed' do expect { host.puppet_agent_dev_package_info( 'collection' ) }.to raise_error( ArgumentError, /^Must\ provide\ puppet_agent_version/ ) end it 'raises an error on unknown platforms' do @platform = 'ubuntu-14.04-x86_64' expect { host.puppet_agent_dev_package_info( 'collection', 'version' ) }.to raise_error( ArgumentError, /^puppet_agent\ dev\ package\ info\ unknown/ ) end it 'calls out to the right method for solaris & returns what it gets' do @platform = 'solaris-10-x86_64' release_path_end_correct = 'release_path_end_correct_1' release_file_correct = 'release_file_correct_1' allow( host ).to receive( :solaris_puppet_agent_dev_package_info ) { [release_path_end_correct, release_file_correct] } release_path_end, release_file = host.puppet_agent_dev_package_info( 'pa_collection', 'pa_version' ) expect( release_path_end ).to be === release_path_end_correct expect( release_file ).to be === release_file_correct end it 'sets up sles|aix platforms correctly' do @platform = 'sles-12-arch' release_path_end, release_file = host.puppet_agent_dev_package_info( 'pa_collection', 'pa_version1' ) expect( release_path_end ).to be === "sles/12/pa_collection/arch" expect( release_file ).to be === "puppet-agent-pa_version1-1.sles12.arch.rpm" end it 'sets up opensuse platforms correctly' do @platform = 'opensuse-15-x86_64' release_path_end, release_file = host.puppet_agent_dev_package_info( 'pa_collection', 'pa_version1' ) expect( release_path_end ).to be === "sles/15/pa_collection/x86_64" expect( release_file ).to be === "puppet-agent-pa_version1-1.sles15.x86_64.rpm" end it 'sets the arch correctly on aix-power platforms' do @platform = 'aix-6.1-power' release_path_end, release_file = host.puppet_agent_dev_package_info( 'pa_collection', '6.0.0' ) expect( release_path_end ).to be === "aix/6.1/pa_collection/ppc" expect( release_file ).to be === "puppet-agent-6.0.0-1.aix6.1.ppc.rpm" end end describe '#pe_puppet_agent_promoted_package_info' do context 'on ubuntu platforms' do it 'splits the platform string version to get puppet-agent packages (format 9999)' do @platform = 'ubuntu-9999-x42' _, _, download_file = host.pe_puppet_agent_promoted_package_info( 'pa_collection' ) expect( download_file ).to match( /-ubuntu-99\.99-x42/ ) end it 'skips splitting the platform string version to get puppet-agent packages when unnecessary (format 99.99)' do @platform = 'ubuntu-88.88-x63' _, _, download_file = host.pe_puppet_agent_promoted_package_info( 'pa_collection' ) expect( download_file ).to match( /-ubuntu-88\.88-x63/ ) end end end describe '#external_copy_base' do it 'returns /root in general' do copy_base = host.external_copy_base expect( copy_base ).to be === '/root' end it 'returns /root if solaris but not version 10' do @platform = 'solaris-11-arch' copy_base = host.external_copy_base expect( copy_base ).to be === '/root' end it 'returns / if on a solaris 10 platform' do @platform = 'solaris-10-arch' copy_base = host.external_copy_base expect( copy_base ).to be === '/' end end describe '#determine_ssh_server' do it 'returns :openssh' do expect( host.determine_ssh_server ).to be === :openssh end end describe '#validate_setup' do it 'does nothing for non cisco_nexus-7 platforms' do @platform = 'el-7-x86_64' validate_test = host.validate_setup expect( validate_test ).to be_nil end end end end beaker-4.30.0/spec/beaker/host/windows/000077500000000000000000000000001407603575700176775ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/host/windows/exec_spec.rb000066400000000000000000000071041407603575700221640ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Windows::Exec do class WindowsExecTest include Unix::Exec include Windows::Exec def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { WindowsExecTest.new(opts, logger) } describe '#prepend_commands' do it 'sets spacing correctly if both parts are defined' do allow( instance ).to receive( :is_cygwin? ).and_return( true ) command_str = instance.prepend_commands( 'command', 'pants', { :cmd_exe => true } ) expect( command_str ).to be === 'cmd.exe /c pants' end it 'sets spacing empty if one is not supplied' do allow( instance ).to receive( :is_cygwin? ).and_return( true ) command_str = instance.prepend_commands( 'command', 'pants' ) expect( command_str ).to be === 'pants' end it 'does not use cmd.exe by default' do allow( instance ).to receive( :is_cygwin? ).and_return( true ) command_str = instance.prepend_commands( 'pants' ) expect( command_str ).not_to match( /cmd\.exe/ ) end end describe '#selinux_enabled?' do it 'does not call selinuxenabled' do expect(Beaker::Command).not_to receive(:new).with("sudo selinuxenabled") expect(instance).not_to receive(:exec).with(0, :accept_all_exit_codes => true) expect(instance.selinux_enabled?).to be === false end end describe '#reboot' do it 'invokes the correct command on the host' do expect( Beaker::Command ).to receive( :new ).with( /^shutdown \/f \/r \/t 0 \/d p:4:1 \/c "Beaker::Host reboot command issued"/ ).and_return( :foo ) expect( instance ).to receive( :exec ).with( :foo, :reset_connection => true ) expect( instance ).to receive( :sleep ) instance.reboot end end describe '#cygwin_installed?' do let (:response) { double( 'response' ) } it 'uses cygcheck to see if cygwin is installed' do expect( Beaker::Command ).to receive(:new).with("cygcheck --check-setup cygwin").and_return(:foo) expect( instance ).to receive( :exec ).with(:foo, :accept_all_exit_codes => true).and_return(response) expect( response ).to receive(:stdout).and_return('cygwin OK') expect(instance.cygwin_installed?).to eq(true) end it 'returns false when unable to find matching text' do expect( Beaker::Command ).to receive(:new).with("cygcheck --check-setup cygwin").and_return(:foo) expect( instance ).to receive( :exec ).with(:foo, :accept_all_exit_codes => true).and_return(response) expect( response ).to receive(:stdout).and_return('No matching text') expect(instance.cygwin_installed?).to eq(false) end end context 'mv' do let(:origin) { '/origin/path/of/content' } let(:destination) { '/destination/path/of/content' } it 'rm first' do expect( instance ).to receive(:execute).with("rm -rf #{destination}").and_return(0) expect( instance ).to receive(:execute).with("mv \"#{origin}\" \"#{destination}\"").and_return(0) expect( instance.mv(origin, destination) ).to be === 0 end it 'does not rm' do expect( instance ).to receive(:execute).with("mv \"#{origin}\" \"#{destination}\"").and_return(0) expect( instance.mv(origin, destination, false) ).to be === 0 end end end end beaker-4.30.0/spec/beaker/host/windows/file_spec.rb000066400000000000000000000033101407603575700221520ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Windows::File do let (:user) { 'someuser' } let (:group) { 'somegroup' } let (:path) { 'C:\Foo\Bar' } let (:newpath) { '/Foo/Bar' } let(:host) { make_host( 'name', { :platform => 'windows' } ) } describe '#chown' do it 'calls cygpath first' do expect( host ).to receive( :execute ).with( "cygpath -u #{path}" ) expect( host ).to receive( :execute ).with( /chown/ ) host.chown( user, path ) end it 'passes cleaned path to super' do allow_any_instance_of( Windows::Host ).to receive( :execute ).with( /cygpath/ ).and_return( newpath ) expect_any_instance_of( Unix::Host ).to receive( :chown ).with( user, newpath , true) host.chown( user, path, true ) end end describe '#chgrp' do it 'calls cygpath first' do expect( host ).to receive( :execute ).with( "cygpath -u #{path}" ).and_return( path ) expect( host ).to receive( :execute ).with( "chgrp #{group} #{path}" ) host.chgrp( group, path ) end it 'passes cleaned path to super' do allow_any_instance_of( Windows::Host ).to receive( :execute ).with( /cygpath/ ).and_return( newpath ) expect_any_instance_of( Unix::Host ).to receive( :chgrp ).with( group, newpath , true) host.chgrp( group, path, true ) end end describe '#ls_ld' do let(:result) { Beaker::Result.new(host, 'ls') } it 'calls cygpath first' do expect( host ).to receive( :execute ).with( "cygpath -u #{path}" ).and_return( path ) expect( host ).to receive( :execute ).with( "ls -ld #{path}" ) host.ls_ld( path ) end end end end beaker-4.30.0/spec/beaker/host/windows/group_spec.rb000066400000000000000000000021471407603575700223760ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Windows::Group do class WindowsGroupTest include Windows::Group end let(:instance) { WindowsGroupTest.new } let(:result) { double(:result, :stdout => group_list_output) } let(:group_list_output) do <<-EOS Name=Foo Name=Bar6 EOS end def add_group(group_name) group_list_output << <<-EOS Name=#{group_name} EOS end before(:each) do expect( instance ).to receive(:execute).with(/wmic group where/).and_yield(result) end it "gets a group_list" do expect(instance.group_list).to eql(["Foo", "Bar6"]) end it "gets groups with spaces" do add_group("With Spaces") expect(instance.group_list).to eql(["Foo", "Bar6", "With Spaces"]) end it "gets groups with dashes" do add_group("With-Dashes") expect(instance.group_list).to eql(["Foo", "Bar6", "With-Dashes"]) end it "gets groups with underscores" do add_group("With_Underscores") expect(instance.group_list).to eql(["Foo", "Bar6", "With_Underscores"]) end end end beaker-4.30.0/spec/beaker/host/windows/pkg_spec.rb000066400000000000000000000017761407603575700220320ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Windows::Pkg do class WindowsPkgTest include Windows::Pkg def initialize(hash, logger) @hash = hash @logger = logger end def [](k) @hash[k] end def to_s "me" end def exec #noop end end let (:opts) { @opts || {} } let (:logger) { double( 'logger' ).as_null_object } let (:instance) { WindowsPkgTest.new(opts, logger) } describe '#install_package' do before :each do allow( instance ).to receive( :identify_windows_architecture ) end context 'cygwin does not exist' do before :each do allow( instance ).to receive( :check_for_command ).and_return( false ) end it 'curls the SSL URL for cygwin\'s installer' do allow( instance ).to receive( :execute ).with( /^setup\-x86/ ).ordered instance.install_package( 'curl' ) end end end end end beaker-4.30.0/spec/beaker/host/windows/user_spec.rb000066400000000000000000000020121407603575700222070ustar00rootroot00000000000000require 'spec_helper' class WindowsUserTest include Windows::User end describe WindowsUserTest do let( :wmic_output ) do <<-EOS Name=Administrator Name=bob foo Name=bob-dash Name=bob.foo Name=cyg_server EOS end let( :command ) { 'cmd /c echo "" | wmic useraccount where localaccount="true" get name /format:value' } let( :host ) { double.as_null_object } let( :result ) { Beaker::Result.new( host, command ) } describe '#user_list' do it 'returns user names list correctly' do result.stdout = wmic_output expect( subject ).to receive( :execute ).with( command ).and_yield(result) expect( subject.user_list ).to be === ['Administrator', 'bob foo', 'bob-dash', 'bob.foo', 'cyg_server'] end it 'yields correctly with the result object' do result.stdout = wmic_output expect( subject ).to receive( :execute ).and_yield(result) subject.user_list { |result| expect( result.stdout ).to be === wmic_output } end end end beaker-4.30.0/spec/beaker/host/windows_spec.rb000066400000000000000000000052621407603575700212430ustar00rootroot00000000000000require 'spec_helper' def bitvise_check_output which case which when :failure # Windows2003r2 failure output: < Beaker::Platform.new( @platform) } else { :platform => Beaker::Platform.new( 'windows-vers-arch-extra' ) } end } let(:host) { make_host( 'name', options.merge(platform) ) } describe '#determine_ssh_server' do it 'does not care about return codes from the execute call' do expect( host ).to receive( :execute ).with( anything, :accept_all_exit_codes => true ) host.determine_ssh_server end it 'uses the default (:openssh) when the execute call fails' do output = bitvise_check_output( :failure ) allow( host ).to receive( :execute ).and_return( output ) expect( host.determine_ssh_server ).to be === :openssh end it 'reads bitvise status correctly' do output = bitvise_check_output( :success ) allow( host ).to receive( :execute ).and_return( output ) expect( host.determine_ssh_server ).to be === :bitvise end it 'returns old value if it has already determined before' do ssh_server_before = host.instance_variable_get( :@ssh_server ) test_value = :test916 host.instance_variable_set( :@ssh_server, test_value ) expect( host ).not_to receive( :execute ) expect( host ).not_to receive( :logger ) expect( host.determine_ssh_server ).to be === test_value host.instance_variable_set( :@ssh_server, ssh_server_before ) end end describe '#external_copy_base' do it 'returns previously calculated value if set' do external_copy_base_before = host.instance_variable_get( :@external_copy_base ) test_value = :testn8265 host.instance_variable_set( :@external_copy_base, test_value ) expect( host ).not_to receive( :execute ) expect( host.external_copy_base ).to be === test_value host.instance_variable_set( :@external_copy_base, external_copy_base_before ) end end end end beaker-4.30.0/spec/beaker/host_prebuilt_steps_spec.rb000066400000000000000000000632621407603575700227010ustar00rootroot00000000000000require 'spec_helper' describe Beaker do let( :options ) { make_opts.merge({ 'logger' => double().as_null_object }) } let( :ntpserver_set ) { "ntp_server_set" } let( :options_ntp ) { make_opts.merge({ 'ntp_server' => ntpserver_set }) } let( :ntpserver ) { Beaker::HostPrebuiltSteps::NTPSERVER } let( :apt_cfg ) { Beaker::HostPrebuiltSteps::APT_CFG } let( :ips_pkg_repo ) { Beaker::HostPrebuiltSteps::IPS_PKG_REPO } let( :sync_cmd ) { Beaker::HostPrebuiltSteps::ROOT_KEYS_SYNC_CMD } let( :windows_pkgs ) { Beaker::HostPrebuiltSteps::WINDOWS_PACKAGES } let( :unix_only_pkgs ) { Beaker::HostPrebuiltSteps::UNIX_PACKAGES } let( :sles_only_pkgs ) { Beaker::HostPrebuiltSteps::SLES_PACKAGES } let( :rhel8_packages ) { Beaker::HostPrebuiltSteps::RHEL8_PACKAGES } let( :fedora_packages) { Beaker::HostPrebuiltSteps::FEDORA_PACKAGES } let( :platform ) { @platform || 'unix' } let( :ip ) { "ip.address.0.0" } let( :stdout) { @stdout || ip } let( :hosts ) { hosts = make_hosts( { :stdout => stdout, :platform => platform } ) hosts[0][:roles] = ['agent'] hosts[1][:roles] = ['master', 'dashboard', 'agent', 'database'] hosts[2][:roles] = ['agent'] hosts } let( :dummy_class ) { Class.new { include Beaker::HostPrebuiltSteps } } shared_examples 'enables_root_login' do |platform, commands, non_cygwin| subject { dummy_class.new } it "can enable root login on #{platform}" do hosts = make_hosts( { :platform => platform, :is_cygwin => non_cygwin} ) if commands.empty? expect( Beaker::Command ).to receive( :new ).exactly( 0 ).times end commands.each do | command | expect( Beaker::Command ).to receive( :new ).with(command).exactly( 3 ).times end subject.enable_root_login( hosts, options ) end end it_should_behave_like 'enables_root_login', 'f5', [] # Non-cygwin Windows it_should_behave_like 'enables_root_login', 'pswindows', [], false # Non-cygwin Windows it_should_behave_like 'enables_root_login', 'windows', [ "sed -ri 's/^#?PermitRootLogin /PermitRootLogin yes/' /etc/sshd_config" ], true # FreeBSD it_should_behave_like 'enables_root_login', 'freesbd', [ "sudo su -c \"sed -ri 's/^#?PermitRootLogin no|^#?PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config\"" ], true it_should_behave_like 'enables_root_login', 'osx-10.10', [ "sudo sed -i '' 's/#PermitRootLogin yes/PermitRootLogin Yes/g' /etc/sshd_config", "sudo sed -i '' 's/#PermitRootLogin no/PermitRootLogin Yes/g' /etc/sshd_config" ] it_should_behave_like 'enables_root_login', 'osx-10.11', [ "sudo sed -i '' 's/#PermitRootLogin yes/PermitRootLogin Yes/g' /private/etc/ssh/sshd_config", "sudo sed -i '' 's/#PermitRootLogin no/PermitRootLogin Yes/g' /private/etc/ssh/sshd_config" ] it_should_behave_like 'enables_root_login', 'osx-10.12', [ "sudo sed -i '' 's/#PermitRootLogin yes/PermitRootLogin Yes/g' /private/etc/ssh/sshd_config", "sudo sed -i '' 's/#PermitRootLogin no/PermitRootLogin Yes/g' /private/etc/ssh/sshd_config" ] it_should_behave_like 'enables_root_login', 'osx-10.13', [ "sudo sed -i '' 's/#PermitRootLogin yes/PermitRootLogin Yes/g' /private/etc/ssh/sshd_config", "sudo sed -i '' 's/#PermitRootLogin no/PermitRootLogin Yes/g' /private/etc/ssh/sshd_config" ] # Solaris it_should_behave_like 'enables_root_login', 'solaris-10', [ "sudo -E svcadm restart network/ssh", "sudo gsed -i -e 's/#PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config" ], true it_should_behave_like 'enables_root_login', 'solaris-11', [ "sudo -E svcadm restart network/ssh", "sudo gsed -i -e 's/PermitRootLogin no/PermitRootLogin yes/g' /etc/ssh/sshd_config", "if grep \"root::::type=role\" /etc/user_attr; then sudo rolemod -K type=normal root; else echo \"root user already type=normal\"; fi" ], true ['debian','ubuntu','cumulus'].each do | deb_like | it_should_behave_like 'enables_root_login', deb_like, [ "sudo su -c \"sed -ri 's/^#?PermitRootLogin no|^#?PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config\"", "sudo su -c \"service ssh restart\"" ] end ['centos','el-','redhat','fedora','eos'].each do | redhat_like | it_should_behave_like 'enables_root_login', redhat_like, [ "sudo su -c \"sed -ri 's/^#?PermitRootLogin no|^#?PermitRootLogin yes/PermitRootLogin yes/' /etc/ssh/sshd_config\"", "sudo -E /sbin/service sshd reload" ] end context 'timesync' do subject { dummy_class.new } it "can sync time on unix hosts" do hosts = make_hosts( { :platform => 'unix' } ) expect( Beaker::Command ).to receive( :new ).with("ntpdate -u -t 20 #{ntpserver}").exactly( 3 ).times subject.timesync( hosts, options ) end it "can retry on failure on unix hosts" do hosts = make_hosts( { :platform => 'unix', :exit_code => [1, 0] } ) allow( subject ).to receive( :sleep ).and_return(true) expect( Beaker::Command ).to receive( :new ).with("ntpdate -u -t 20 #{ntpserver}").exactly( 6 ).times subject.timesync( hosts, options ) end it "eventually gives up and raises an error when unix hosts can't be synched" do hosts = make_hosts( { :platform => 'unix', :exit_code => 1 } ) allow( subject ).to receive( :sleep ).and_return(true) expect( Beaker::Command ).to receive( :new ).with("ntpdate -u -t 20 #{ntpserver}").exactly( 5 ).times expect{ subject.timesync( hosts, options ) }.to raise_error(/NTP date was not successful after/) end it "can sync time on windows hosts" do hosts = make_hosts( { :platform => 'windows' } ) expect( Beaker::Command ).to receive( :new ).with("w32tm /register").exactly( 3 ).times expect( Beaker::Command ).to receive( :new ).with("net start w32time").exactly( 3 ).times expect( Beaker::Command ).to receive( :new ).with("w32tm /config /manualpeerlist:#{ntpserver} /syncfromflags:manual /update").exactly( 3 ).times expect( Beaker::Command ).to receive( :new ).with("w32tm /resync").exactly( 3 ).times subject.timesync( hosts, options ) end it "can sync time on Sles hosts" do hosts = make_hosts( { :platform => 'sles-13.1-x64' } ) expect( Beaker::Command ).to receive( :new ).with("sntp #{ntpserver}").exactly( 3 ).times subject.timesync( hosts, options ) end it "can sync time on RHEL8 hosts" do hosts = make_hosts(:platform => 'el-8-x86_x64') expect(Beaker::Command).to receive(:new) .with("chronyc add server #{ntpserver} prefer trust;chronyc makestep;chronyc burst 1/2") .exactly(3) .times subject.timesync(hosts, options) end it "can sync time on Fedora hosts" do hosts = make_hosts(:platform => 'fedora-32-x86_64') expect(Beaker::Command).to receive(:new) .with("chronyc add server #{ntpserver} prefer trust;chronyc makestep;chronyc burst 1/2") .exactly(3) .times subject.timesync(hosts, options) end it "can set time server on unix hosts" do hosts = make_hosts( { :platform => 'unix' } ) expect( Beaker::Command ).to receive( :new ).with("ntpdate -u -t 20 #{ntpserver_set}").exactly( 3 ).times subject.timesync( hosts, options_ntp ) end it "can set time server on windows hosts" do hosts = make_hosts( { :platform => 'windows' } ) expect( Beaker::Command ).to receive( :new ).with("w32tm /register").exactly( 3 ).times expect( Beaker::Command ).to receive( :new ).with("net start w32time").exactly( 3 ).times expect( Beaker::Command ).to receive( :new ).with("w32tm /config /manualpeerlist:#{ntpserver_set} /syncfromflags:manual /update").exactly( 3 ).times expect( Beaker::Command ).to receive( :new ).with("w32tm /resync").exactly( 3 ).times subject.timesync( hosts, options_ntp ) end it "can set time server on Sles hosts" do hosts = make_hosts( { :platform => 'sles-13.1-x64' } ) expect( Beaker::Command ).to receive( :new ).with("sntp #{ntpserver_set}").exactly( 3 ).times subject.timesync( hosts, options_ntp ) end it "can set time server on RHEL8 hosts" do hosts = make_hosts(:platform => 'el-8-x86_x64') expect(Beaker::Command).to receive(:new) .with("chronyc add server #{ntpserver_set} prefer trust;chronyc makestep;chronyc burst 1/2") .exactly(3) .times subject.timesync(hosts, options_ntp) end end context "apt_get_update" do subject { dummy_class.new } it "can perform apt-get on ubuntu hosts" do host = make_host( 'testhost', { :platform => 'ubuntu' } ) expect( Beaker::Command ).to receive( :new ).with("apt-get update").once subject.apt_get_update( host ) end it "can perform apt-get on debian hosts" do host = make_host( 'testhost', { :platform => 'debian' } ) expect( Beaker::Command ).to receive( :new ).with("apt-get update").once subject.apt_get_update( host ) end it "can perform apt-get on cumulus hosts" do host = make_host( 'testhost', { :platform => 'cumulus' } ) expect( Beaker::Command ).to receive( :new ).with("apt-get update").once subject.apt_get_update( host ) end it "does nothing on non debian/ubuntu/cumulus hosts" do host = make_host( 'testhost', { :platform => 'windows' } ) expect( Beaker::Command ).to receive( :new ).never subject.apt_get_update( host ) end end context "copy_file_to_remote" do subject { dummy_class.new } it "can copy a file to a remote host" do content = "this is the content" tempfilepath = "/path/to/tempfile" filepath = "/path/to/file" host = make_host( 'testhost', { :platform => 'windows' }) tempfile = double( 'tempfile' ) allow( tempfile ).to receive( :path ).and_return( tempfilepath ) allow( Tempfile ).to receive( :open ).and_yield( tempfile ) file = double( 'file' ) allow( File ).to receive( :open ).and_yield( file ) expect( file ).to receive( :puts ).with( content ).once expect( host ).to receive( :do_scp_to ).with( tempfilepath, filepath, subject.instance_variable_get( :@options ) ).once subject.copy_file_to_remote(host, filepath, content) end end context "proxy_config" do subject { dummy_class.new } it "correctly configures ubuntu hosts" do hosts = make_hosts( { :platform => 'ubuntu', :exit_code => 1 } ) expect( Beaker::Command ).to receive( :new ).with( "if test -f /etc/apt/apt.conf; then mv /etc/apt/apt.conf /etc/apt/apt.conf.bk; fi" ).exactly( 3 ) hosts.each do |host| expect( subject ).to receive( :copy_file_to_remote ).with( host, '/etc/apt/apt.conf', apt_cfg ).once expect( subject ).to receive( :apt_get_update ).with( host ).once end subject.proxy_config( hosts, options ) end it "correctly configures debian hosts" do hosts = make_hosts( { :platform => 'debian' } ) expect( Beaker::Command ).to receive( :new ).with( "if test -f /etc/apt/apt.conf; then mv /etc/apt/apt.conf /etc/apt/apt.conf.bk; fi" ).exactly( 3 ).times hosts.each do |host| expect( subject ).to receive( :copy_file_to_remote ).with( host, '/etc/apt/apt.conf', apt_cfg ).once expect( subject ).to receive( :apt_get_update ).with( host ).once end subject.proxy_config( hosts, options ) end it "correctly configures cumulus hosts" do hosts = make_hosts( { :platform => 'cumulus' } ) expect( Beaker::Command ).to receive( :new ).with( "if test -f /etc/apt/apt.conf; then mv /etc/apt/apt.conf /etc/apt/apt.conf.bk; fi" ).exactly( 3 ).times hosts.each do |host| expect( subject ).to receive( :copy_file_to_remote ).with( host, '/etc/apt/apt.conf', apt_cfg ).once expect( subject ).to receive( :apt_get_update ).with( host ).once end subject.proxy_config( hosts, options ) end it "correctly configures solaris-11 hosts" do hosts = make_hosts( { :platform => 'solaris-11' } ) expect( Beaker::Command ).to receive( :new ).with( "/usr/bin/pkg unset-publisher solaris || :" ).exactly( 3 ).times hosts.each do |host| expect( Beaker::Command ).to receive( :new ).with( "/usr/bin/pkg set-publisher -g %s solaris" % ips_pkg_repo ).once end subject.proxy_config( hosts, options ) end it "does nothing for non ubuntu/debian/cumulus/solaris-11 hosts" do hosts = make_hosts( { :platform => 'windows' } ) expect( Beaker::Command ).to receive( :new ).never subject.proxy_config( hosts, options ) end end context "add_el_extras" do subject { dummy_class.new } it 'adds extras for el-6 hosts' do hosts = make_hosts( { :platform => Beaker::Platform.new('el-6-arch'), :exit_code => 1 }, 4 ) hosts[1][:platform] = Beaker::Platform.new('centos-6-arch') hosts[2][:platform] = Beaker::Platform.new('scientific-6-arch') hosts[3][:platform] = Beaker::Platform.new('redhat-6-arch') expect( Beaker::Command ).to receive( :new ).with( "rpm -qa | grep epel-release" ).exactly( hosts.count ).times hosts.each do |host| expect(host).to receive( :install_package_with_rpm ).with( "http://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm", "--replacepkgs", {:package_proxy => false} ).once end expect( Beaker::Command ).to receive( :new ).with( "sed -i -e 's;#baseurl.*$;baseurl=http://dl\\.fedoraproject\\.org/pub/epel/6/$basearch;' /etc/yum.repos.d/epel.repo" ).exactly( hosts.count ).times expect( Beaker::Command ).to receive( :new ).with( "sed -i -e '/mirrorlist/d' /etc/yum.repos.d/epel.repo" ).exactly( hosts.count ).times expect( Beaker::Command ).to receive( :new ).with( "yum clean all && yum makecache" ).exactly( hosts.count ).times subject.add_el_extras( hosts, options ) end it "should do nothing for non el-5/6 hosts" do hosts = make_hosts( { :platform => Beaker::Platform.new('windows-version-arch') } ) expect( Beaker::Command ).to receive( :new ).never subject.add_el_extras( hosts, options ) end end context "sync_root_keys" do subject { dummy_class.new } it "can sync keys on a solaris/eos host" do @platform = 'solaris' expect( Beaker::Command ).to receive( :new ).with( sync_cmd % "bash" ).exactly( 3 ).times subject.sync_root_keys( hosts, options ) end it "can sync keys on a non-solaris host" do expect( Beaker::Command ).to receive( :new ).with( sync_cmd % "env PATH=\"/usr/gnu/bin:$PATH\" bash" ).exactly( 3 ).times subject.sync_root_keys( hosts, options ) end end context "validate_host" do subject { dummy_class.new } it "can validate unix hosts" do hosts.each do |host| unix_only_pkgs.each do |pkg| expect( host ).to receive( :check_for_package ).with( pkg ).once.and_return( false ) expect( host ).to receive( :install_package ).with( pkg ).once end end subject.validate_host(hosts, options) end it "can validate windows hosts" do @platform = 'windows' hosts.each do |host| windows_pkgs.each do |pkg| allow( host ).to receive( :cygwin_installed? ).and_return( true ) allow( host ).to receive( :is_cygwin? ).and_return( true ) expect( host ).to receive( :check_for_package ).with( pkg ).once.and_return( false ) expect( host ).to receive( :install_package ).with( pkg ).once end end subject.validate_host(hosts, options) end it "can validate SLES hosts" do @platform = 'sles-13.1-x64' hosts.each do |host| sles_only_pkgs.each do |pkg| expect( host ).to receive( :check_for_package).with( pkg ).once.and_return( false ) expect( host ).to receive( :install_package ).with( pkg ).once end end subject.validate_host(hosts, options) end it "can validate opensuse hosts" do @platform = 'opensuse-15-x86_x64' hosts.each do |host| sles_only_pkgs.each do |pkg| expect( host ).to receive( :check_for_package).with( pkg ).once.and_return( false ) expect( host ).to receive( :install_package ).with( pkg ).once end end subject.validate_host(hosts, options) end it "can validate RHEL8 hosts" do @platform = 'el-8-x86_x64' hosts.each do |host| rhel8_packages.each do |pkg| expect(host).to receive(:check_for_package).with(pkg).once.and_return(false) expect(host).to receive(:install_package).with(pkg).once end end subject.validate_host(hosts, options) end it "can validate Fedora hosts" do @platform = 'fedora-32-x86_64' hosts.each do |host| fedora_packages.each do |pkg| expect(host).to receive(:check_for_package).with(pkg).once.and_return(false) expect(host).to receive(:install_package).with(pkg).once end end subject.validate_host(hosts, options) end it 'skips validation on cisco hosts' do @platform = 'cisco_nexus-7-x86_64' expect( subject ).to receive( :check_and_install_packages_if_needed ).never subject.validate_host(hosts, options) end end context 'get_domain_name' do subject { dummy_class.new } shared_examples 'find domain name' do it "finds the domain name" do expect( subject.get_domain_name( host ) ).to be === "labs.lan" end end context "on windows" do let(:host) { make_host( 'name', { :platform => 'windows', :is_cygwin => cygwin, :stdout => "domain labs.lan d.labs.net dc1.labs.net labs.com\nnameserver 10.16.22.10\nnameserver 10.16.22.11", } ) } context "with cygwin" do let(:cygwin) { true } before(:each) do expect( Beaker::Command ).to receive( :new ).with( "cat /cygdrive/c/Windows/System32/drivers/etc/hosts" ).once end include_examples 'find domain name' end context "without cygwin" do let(:cygwin) { false } before(:each) do expect( Beaker::Command ).to receive( :new ).with( 'type C:\Windows\System32\drivers\etc\hosts' ).once end include_examples 'find domain name' end end ['centos','redhat'].each do |platform| context "on platform '#{platform}'" do let(:host) { make_host( 'name', { :platform => platform, :stdout => stdout, } ) } before(:each) do expect( Beaker::Command ).to receive( :new ).with( "cat /etc/resolv.conf" ).once end context "with a domain entry" do let(:stdout) { "domain labs.lan d.labs.net dc1.labs.net labs.com\nnameserver 10.16.22.10\nnameserver 10.16.22.11" } include_examples 'find domain name' end context "with a search entry" do let(:stdout) { "search labs.lan d.labs.net dc1.labs.net labs.com\nnameserver 10.16.22.10\nnameserver 10.16.22.11" } include_examples 'find domain name' end context "with a both a domain and a search entry" do let(:stdout) { "domain labs.lan\nsearch d.labs.net dc1.labs.net labs.com\nnameserver 10.16.22.10\nnameserver 10.16.22.11" } include_examples 'find domain name' end context "with a both a domain and a search entry, the search entry first" do let(:stdout) { "search foo.example.net\ndomain labs.lan d.labs.net dc1.labs.net labs.com\nnameserver 10.16.22.10\nnameserver 10.16.22.11" } include_examples 'find domain name' end end end end context "get_ip" do subject { dummy_class.new } it "can exec the get_ip command" do host = make_host('name', { :stdout => "192.168.2.130\n" } ) expect( Beaker::Command ).to receive( :new ).with( "ip a | awk '/global/{print$2}' | cut -d/ -f1 | head -1", [], {:prepend_cmds=>nil, :cmdexe=>false} ).once expect( subject.get_ip( host ) ).to be === "192.168.2.130" end it "can exec the get_ip command with tail with vagrant hypervisor" do host = make_host('name', { :stdout => "192.168.2.131\n", :hypervisor => "vagrant" } ) expect( Beaker::Command ).to receive( :new ).with( "ip a | awk '/global/{print$2}' | cut -d/ -f1 | tail -1", [], {:prepend_cmds=>nil, :cmdexe=>false} ).once expect( subject.get_ip( host ) ).to be === "192.168.2.131" end end context "set_etc_hosts" do subject { dummy_class.new } it "can set the /etc/hosts string on a host" do host = make_host('name', {}) etc_hosts = "127.0.0.1 localhost\n192.168.2.130 pe-ubuntu-lucid\n192.168.2.128 pe-centos6\n192.168.2.131 pe-debian6" expect( Beaker::Command ).to receive( :new ).with( "echo '#{etc_hosts}' >> /etc/hosts" ).once expect( host ).to receive( :exec ).once subject.set_etc_hosts(host, etc_hosts) end end context "copy_ssh_to_root" do subject { dummy_class.new } it "can copy ssh to root in windows hosts with no cygwin" do host = make_host( 'testhost', { :platform => 'windows', :is_cygwin => false }) expect( Beaker::Command ).to receive( :new ).with( "if exist .ssh (xcopy .ssh C:\\Users\\Administrator\\.ssh /s /e /y /i)" ).once subject.copy_ssh_to_root(host, options) end end context "package_proxy" do subject { dummy_class.new } proxyurl = "http://192.168.2.100:3128" it "can set proxy config on a debian/ubuntu/cumulus host" do host = make_host('name', { :platform => 'cumulus' } ) expect( Beaker::Command ).to receive( :new ).with( "echo 'Acquire::http::Proxy \"#{proxyurl}/\";' >> /etc/apt/apt.conf.d/10proxy" ).once expect( host ).to receive( :exec ).once subject.package_proxy(host, options.merge( {'package_proxy' => proxyurl}) ) end ['centos','redhat'].each do |platform| it "can set proxy config on a '#{platform}' host" do host = make_host('name', { :platform => platform } ) expect( Beaker::Command ).to receive( :new ).with( "echo 'proxy=#{proxyurl}/' >> /etc/yum.conf" ).once expect( host ).to receive( :exec ).once subject.package_proxy(host, options.merge( {'package_proxy' => proxyurl}) ) end end end context "set_env" do subject { dummy_class.new } it "sets user ssh environment on an OS X 10.10 host" do test_host_ssh_calls('osx-10.10') end it "sets user ssh environment on an OS X 10.11 host" do test_host_ssh_calls('osx-10.11') end it "sets user ssh environment on an OS X 10.12 host" do test_host_ssh_calls('osx-10.12') end it "sets user ssh environment on an OS X 10.13 host" do test_host_ssh_calls('osx-10.13') end it "sets user ssh environment on an ssh-based linux host" do test_host_ssh_calls('ubuntu') end it "sets user ssh environment on an sshd-based linux host" do test_host_ssh_calls('eos') end it "sets user ssh environment on an sles host" do test_host_ssh_calls('sles') end it "sets user ssh environment on a solaris host" do test_host_ssh_calls('solaris') end it "sets user ssh environment on an aix host" do test_host_ssh_calls('aix') end it "sets user ssh environment on a FreeBSD host" do test_host_ssh_calls('freebsd') end it "sets user ssh environment on a windows host" do test_host_ssh_calls('windows') end it "skips an f5 host correctly" do host = make_host('name', { :platform => 'f5-stuff', :ssh_env_file => 'ssh_env_file', :is_cygwin => true, } ) opts = { :env1_key => :env1_value, :env2_key => :env2_value } allow( host ).to receive( :skip_set_env? ).and_return('f5 say NO' ) expect( subject ).to receive( :construct_env ).exactly(0).times expect( Beaker::Command ).to receive( :new ).exactly(0).times expect( host ).to receive( :add_env_var ).exactly(0).times opts.each_pair do |key, value| expect( host ).to receive( :add_env_var ).with( key, value ).exactly(0).times end expect( host ).to receive( :exec ).exactly(0).times subject.set_env(host, options.merge( opts )) end it 'skips a cisco host correctly' do host = make_host('name', { :platform => 'cisco_nexus-7-x86_64', :ssh_env_file => 'ssh_env_file', :is_cygwin => true, } ) opts = { :env1_key => :env1_value, :env2_key => :env2_value } allow( host ).to receive( :skip_set_env? ).and_return('cisco say NO' ) expect( subject ).to receive( :construct_env ).exactly(0).times expect( Beaker::Command ).to receive( :new ).exactly(0).times expect( host ).to receive( :add_env_var ).exactly(0).times opts.each_pair do |key, value| expect( host ).to receive( :add_env_var ).with( key, value ).exactly(0).times end expect( host ).to receive( :exec ).exactly(0).times subject.set_env(host, options.merge( opts )) end def test_host_ssh_calls(platform_name) host = make_host('name', { :platform => platform_name, :ssh_env_file => 'ssh_env_file', :is_cygwin => true, } ) opts = { :env1_key => :env1_value, :env2_key => :env2_value } allow( host ).to receive( :skip_set_env? ).and_return( nil ) expect( subject ).to receive( :construct_env ).and_return( opts ) expect( host ).to receive( :ssh_permit_user_environment ) expect( host ).to receive( :ssh_set_user_environment ) subject.set_env(host, options.merge( opts )) end end end beaker-4.30.0/spec/beaker/host_spec.rb000066400000000000000000001040641407603575700175510ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Host do let(:options) { @options ? @options : {} } let(:platform) { @platform ? { :platform => @platform } : {} } let(:host) { make_host( 'name', options.merge(platform) ) } it 'creates a windows host given a windows config' do @platform = 'windows' expect( host ).to be_a_kind_of Windows::Host end it 'defaults to a unix host' do expect( host ).to be_a_kind_of Unix::Host end it 'can be read like a hash' do expect{ host['value'] }.to_not raise_error end it 'can be written like a hash' do host['value'] = 'blarg' expect( host['value'] ).to be === 'blarg' end describe "host types" do let(:options) { Beaker::Options::OptionsHash.new } it "can be a pe host" do options['type'] = 'pe' expect(host.is_pe?).to be_truthy expect(host.use_service_scripts?).to be_truthy expect(host.is_using_passenger?).to be_truthy expect(host.graceful_restarts?).to be_falsy end it "can be a foss-source host" do expect(host.is_pe?).to be_falsy expect(host.use_service_scripts?).to be_falsy expect(host.is_using_passenger?).to be_falsy end it "can be a foss-package host" do options['use-service'] = true expect(host.is_pe?).to be_falsy expect(host.use_service_scripts?).to be_truthy expect(host.is_using_passenger?).to be_falsy expect(host.graceful_restarts?).to be_falsy end it "can be a foss-packaged host using passenger" do host.uses_passenger! expect(host.is_pe?).to be_falsy expect(host.use_service_scripts?).to be_truthy expect(host.is_using_passenger?).to be_truthy expect(host.graceful_restarts?).to be_truthy end it 'can be an AIO host' do options['type'] = 'aio' expect(host.is_pe?).to be_falsy expect(host.use_service_scripts?).to be_falsy expect(host.is_using_passenger?).to be_falsy end it 'sets the paths correctly for an AIO host' do options['type'] = 'aio' expect(host['puppetvardir']).to be_nil end end describe "uses_passenger!" do it "sets passenger property" do host.uses_passenger! expect(host['passenger']).to be_truthy expect(host.is_using_passenger?).to be_truthy end it "sets puppetservice" do host.uses_passenger!('servicescript') expect(host['puppetservice']).to eq('servicescript') end it "sets puppetservice to apache2 by default" do host.uses_passenger! expect(host['puppetservice']).to eq('apache2') end end describe "graceful_restarts?" do it "is true if graceful-restarts property is set true" do options['graceful-restarts'] = true expect(host.graceful_restarts?).to be_truthy end it "is false if graceful-restarts property is set false" do options['graceful-restarts'] = false expect(host.graceful_restarts?).to be_falsy end it "is false if is_pe and graceful-restarts is nil" do options['type'] = 'pe' expect(host.graceful_restarts?).to be_falsy end it "is true if is_pe and graceful-restarts is true" do options['type'] = 'pe' options['graceful-restarts'] = true expect(host.graceful_restarts?).to be_truthy end it "falls back to passenger property if not pe and graceful-restarts is nil" do host.uses_passenger! expect(host.graceful_restarts?).to be_truthy end end describe "windows hosts" do describe "install_package" do let(:cygwin) { 'setup-x86.exe' } let(:cygwin64) { 'setup-x86_64.exe' } let(:package) { 'foo' } context "testing osarchitecture" do context "64 bit" do before do @platform = Beaker::Platform.new('windows-2008r2-64') end it "uses 64 bit cygwin" do expect( host ).to receive(:execute).with(/#{cygwin64}.*#{package}/) host.install_package(package) end end context "32 bit" do before do @platform = Beaker::Platform.new('windows-10ent-32') end it "uses 32 bit cygwin" do expect( host ).to receive(:execute).with(/#{cygwin}.*#{package}/) host.install_package(package) end end end end end describe "#add_env_var" do it "does nothing if the key/value pair already exists" do result = Beaker::Result.new(host, '') result.exit_code = 0 expect( Beaker::Command ).to receive(:new).with("grep ^key=.*\\/my\\/first\\/value ~/.ssh/environment") expect( host ).to receive(:exec).once.and_return(result) host.add_env_var('key', '/my/first/value') end it "adds new line to environment file if no env var of that name already exists" do result = Beaker::Result.new(host, '') result.exit_code = 1 expect( Beaker::Command ).to receive(:new).with("grep ^key=.*\\/my\\/first\\/value ~/.ssh/environment") expect( host ).to receive(:exec).and_return(result) expect( Beaker::Command ).to receive(:new).with(/grep \^key= ~\/\.ssh\/environment/) expect( host ).to receive(:exec).and_return(result) expect( Beaker::Command ).to receive(:new).with("echo \"key=/my/first/value\" >> ~/.ssh/environment") host.add_env_var('key', '/my/first/value') end it "updates existing line in environment file when adding additional value to existing variable" do result = Beaker::Result.new(host, '') result.exit_code = 1 expect( Beaker::Command ).to receive(:new).with("grep ^key=.*\\/my\\/first\\/value ~/.ssh/environment") expect( host ).to receive(:exec).and_return(result) result = Beaker::Result.new(host, '') result.exit_code = 0 expect( Beaker::Command ).to receive(:new).with(/grep \^key= ~\/\.ssh\/environment/) expect( host ).to receive(:exec).and_return(result) expect( Beaker::SedCommand ).to receive(:new).with('unix', 's/^key=/key=\\/my\\/first\\/value:/', '~/.ssh/environment') host.add_env_var('key', '/my/first/value') end end describe "#delete_env_var" do it "deletes env var" do expect( Beaker::SedCommand ).to receive(:new).with('unix', '/key=\\/my\\/first\\/value$/d', '~/.ssh/environment') expect( Beaker::SedCommand ).to receive(:new).with("unix", "s/key=\\(.*\\)[;:]\\/my\\/first\\/value/key=\\1/", "~/.ssh/environment") expect( Beaker::SedCommand ).to receive(:new).with("unix", "s/key=\\/my\\/first\\/value[;:]/key=/", "~/.ssh/environment") host.delete_env_var('key', '/my/first/value') end end describe "executing commands" do let(:command) { Beaker::Command.new('ls') } let(:host) { Beaker::Host.create('host', {}, make_host_opts('host', options.merge(platform))) } let(:result) { Beaker::Result.new(host, 'ls') } before :each do result.stdout = 'stdout' result.stderr = 'stderr' logger = double(:logger) allow( logger ).to receive(:host_output) allow( logger ).to receive(:debug) allow( logger ).to receive(:with_indent) { |&block| block.call } host.instance_variable_set :@logger, logger conn = double(:connection) allow( conn ).to receive(:execute).and_return(result) allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.instance_variable_set :@connection, conn end it 'takes a command object and a hash of options' do result.exit_code = 0 expect{ host.exec(command, {}) }.to_not raise_error end it 'acts on the host\'s logger and connection object' do result.exit_code = 0 expect( host.instance_variable_get(:@logger) ).to receive(:debug).at_least(1).times expect( host.instance_variable_get(:@connection) ).to receive(:execute).once host.exec(command) end it 'returns the result object' do result.exit_code = 0 expect( host.exec(command) ).to be === result end it 'logs the amount of time spent executing the command' do result.exit_code = 0 expect(host.logger).to receive(:debug).with(/executed in \d\.\d{2} seconds/) host.exec(command,{}) end it 'raises a CommandFailure when an unacceptable exit code is returned' do result.exit_code = 7 opts = { :acceptable_exit_codes => [0, 1] } expect { host.exec(command, opts) }.to raise_error(Beaker::Host::CommandFailure) end it 'raises a CommandFailure when an unacceptable exit code is returned and the accept_all_exit_codes flag is set to false' do result.exit_code = 7 opts = { :acceptable_exit_codes => [0, 1], :accept_all_exit_codes => false } expect { host.exec(command, opts) }.to raise_error(Beaker::Host::CommandFailure) end it 'does throw an error when an unacceptable exit code is returned and the accept_all_exit_codes flag is set' do result.exit_code = 7 opts = { :acceptable_exit_codes => [0, 1], :accept_all_exit_codes => true } allow( host.logger ).to receive( :warn ) expect { host.exec(command, opts) }.to raise_error end it 'sends a warning when both :acceptable_exit_codes & :accept_all_exit_codes are set' do result.exit_code = 1 opts = { :acceptable_exit_codes => [0, 1], :accept_all_exit_codes => true } expect( host.logger ).to receive( :warn ).with( /overrides/ ) expect { host.exec(command, opts) }.to_not raise_error end it 'explicitly closes the connection when :reset_connection is set' do expect( host ).to receive( :close ) expect { host.exec(command, :reset_connection => true) }.to_not raise_error end context "controls the result objects logging" do it "and passes a test if the exit_code doesn't match the default :acceptable_exit_codes of 0" do result.exit_code = 0 expect{ host.exec(command,{}) }.to_not raise_error end it "and fails a test if the exit_code doesn't match the default :acceptable_exit_codes of 0" do result.exit_code = 1 expect{ host.exec(command,{}) }.to raise_error end it "and passes a test if the exit_code matches :acceptable_exit_codes" do result.exit_code = 0 expect{ host.exec(command,{:acceptable_exit_codes => 0}) }.to_not raise_error end it "and fails a test if the exit_code doesn't match :acceptable_exit_codes" do result.exit_code = 0 expect{ host.exec(command,{:acceptable_exit_codes => 1}) }.to raise_error end it "and passes a test if the exit_code matches one of the :acceptable_exit_codes" do result.exit_code = 127 expect{ host.exec(command,{:acceptable_exit_codes => [1,127]}) }.to_not raise_error end it "and passes a test if the exit_code matches one of the range of :acceptable_exit_codes" do result.exit_code = 1 expect{ host.exec(command,{:acceptable_exit_codes => (0..127)}) }.to_not raise_error end end end describe "#mkdir_p" do it "does the right thing on a bash host, identified as is_cygwin=true" do @options = {:is_cygwin => true} @platform = 'windows' result = double allow( result ).to receive( :exit_code ).and_return( 0 ) allow( host ).to receive( :exec ).and_return( result ) expect( Beaker::Command ).to receive(:new).with("mkdir -p \"test/test/test\"") expect( host.mkdir_p('test/test/test') ).to be == true end it "does the right thing on a bash host, identified as is_cygwin=nil" do @options = {:is_cygwin => nil} @platform = 'windows' result = double allow( result ).to receive( :exit_code ).and_return( 0 ) allow( host ).to receive( :exec ).and_return( result ) expect( Beaker::Command ).to receive(:new).with("mkdir -p \"test/test/test\"") expect( host.mkdir_p('test/test/test') ).to be == true end it "does the right thing on a non-bash host, identified as is_cygwin=false (powershell)" do @options = {:is_cygwin => false} @platform = 'windows' result = double allow( result ).to receive( :exit_code ).and_return( 0 ) allow( host ).to receive( :exec ).and_return( result ) expect( Beaker::Command ).to receive(:new). with("powershell.exe", ["-ExecutionPolicy Bypass", "-InputFormat None", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command New-Item -Path 'test\\test\\test' -ItemType 'directory'"]) expect( host.mkdir_p('test/test/test') ).to be == true end end describe "#touch" do it "generates the right absolute command for a windows host" do @platform = 'windows' expect( host.touch('touched_file') ).to be == "c:\\\\windows\\\\system32\\\\cmd.exe /c echo. 2> touched_file" end ['centos','redhat'].each do |platform| it "generates the right absolute command for a #{platform} host" do @platform = platform expect( host.touch('touched_file') ).to be == "/bin/touch touched_file" end end it "generates the right absolute command for an osx host" do @platform = 'osx' expect( host.touch('touched_file') ).to be == "/usr/bin/touch touched_file" end end context 'do_scp_to' do # it takes a location and a destination # it basically proxies that to the connection object it 'do_scp_to logs info and proxies to the connection' do create_files(['source']) logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ '/source', 'target', {} ] conn_args = args expect( logger ).to receive(:trace) expect( conn ).to receive(:scp_to).with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.do_scp_to *args end it 'calls for host scp post operations after SCPing happens' do create_files(['source']) logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ '/source', 'target', {} ] conn_args = args allow( logger ).to receive(:trace) expect( conn ).to receive(:scp_to).ordered.with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) expect( host ).to receive( :scp_post_operations ).ordered host.do_scp_to *args end it 'throws an IOError when the file given doesn\'t exist' do expect { host.do_scp_to "/does/not/exist", "does/not/exist/over/there", {} }.to raise_error(IOError) end context "using an ignore array with an absolute source path" do let( :source_path ) { '/repos/puppetlabs-inifile' } let( :target_path ) { '/etc/puppetlabs/modules/inifile' } before :each do test_dir = "#{source_path}/tests" other_test_dir = "#{source_path}/tests2" files = [ '00_EnvSetup.rb', '035_StopFirewall.rb', '05_HieraSetup.rb', '01_TestSetup.rb', '03_PuppetMasterSanity.rb', '06_InstallModules.rb','02_PuppetUserAndGroup.rb', '04_ValidateSignCert.rb', '07_InstallCACerts.rb' ] @fileset1 = files.shuffle.map {|file| test_dir + '/' + file } @fileset2 = files.shuffle.map {|file| other_test_dir + '/' + file } create_files( @fileset1 ) create_files( @fileset2 ) end it 'can take an ignore list that excludes all files and not call scp_to' do logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ source_path, target_path, {:ignore => ['tests', 'tests2']} ] expect( logger ).to receive(:trace) expect( host ).to receive( :mkdir_p ).exactly(0).times expect( conn ).to receive(:scp_to).exactly(0).times host.do_scp_to *args end it 'can take an ignore list that excludes a single file and scp the rest' do created_target_path = File.join(target_path, File.basename(source_path)) exclude_file = '07_InstallCACerts.rb' logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ source_path, target_path, {:ignore => [exclude_file], :dry_run => false} ] allow( Dir ).to receive( :glob ).and_return( @fileset1 + @fileset2 ) expect( logger ).to receive(:trace) expect( host ).to receive( :mkdir_p ).with("#{created_target_path}/tests") expect( host ).to receive( :mkdir_p ).with("#{created_target_path}/tests2") (@fileset1 + @fileset2).each do |file| if file !~ /#{exclude_file}/ file_args = [ file, File.join(created_target_path, File.dirname(file).gsub(source_path,'')), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to receive(:scp_to).with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) else file_args = [ file, File.join(created_target_path, File.dirname(file).gsub(source_path,'')), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to_not receive(:scp_to).with( *conn_args ) end end allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.do_scp_to *args end end context "using an ignore array with an absolute source path in host root" do let( :source_path ) { '/puppetlabs-inifile' } let( :target_path ) { '/etc/puppetlabs/modules/inifile' } before :each do test_dir = "#{source_path}/tests" other_test_dir = "#{source_path}/tests/tests2" another_test_dir = "#{source_path}/tests/tests3" files = [ '00_EnvSetup.rb', '035_StopFirewall.rb', '05_HieraSetup.rb', '01_TestSetup.rb', '03_PuppetMasterSanity.rb', '06_InstallModules.rb','02_PuppetUserAndGroup.rb', '04_ValidateSignCert.rb', '07_InstallCACerts.rb' ] @fileset1 = files.shuffle.map {|file| test_dir + '/' + file } @fileset2 = files.shuffle.map {|file| other_test_dir + '/' + file } @fileset3 = files.shuffle.map {|file| another_test_dir + '/' + file } create_files( @fileset1 ) create_files( @fileset2 ) create_files( @fileset3 ) end it "should create target dirs with correct path seperator" do create_files(['source']) exclude_file = '04_ValidateSignCert.rb' logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ source_path, target_path, {:ignore => [exclude_file]} ] conn_args = args allow( Dir ).to receive( :glob ).and_return( @fileset1 + @fileset2 + @fileset3) created_target_path = File.join(target_path, File.basename(source_path)) expect( host ).to receive( :mkdir_p ).with("#{created_target_path}/tests") expect( host ).to receive( :mkdir_p ).with("#{created_target_path}/tests/tests2") expect( host ).to receive( :mkdir_p ).with("#{created_target_path}/tests/tests3") (@fileset1 + @fileset2 + @fileset3).each do |file| if file !~ /#{exclude_file}/ file_args = [ file, File.join(created_target_path, File.dirname(file).gsub(source_path,'')), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to receive(:scp_to).with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) else file_args = [ file, File.join(created_target_path, File.dirname(file).gsub(source_path,'')), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to_not receive(:scp_to).with( *conn_args ) end end allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.do_scp_to *args end end context "using an ignore array" do before :each do test_dir = 'tmp/tests' other_test_dir = 'tmp/tests2' files = [ '00_EnvSetup.rb', '035_StopFirewall.rb', '05_HieraSetup.rb', '01_TestSetup.rb', '03_PuppetMasterSanity.rb', '06_InstallModules.rb','02_PuppetUserAndGroup.rb', '04_ValidateSignCert.rb', '07_InstallCACerts.rb' ] @fileset1 = files.shuffle.map {|file| test_dir + '/' + file } @fileset2 = files.shuffle.map {|file| other_test_dir + '/' + file } create_files( @fileset1 ) create_files( @fileset2 ) end it 'can take an ignore list that excludes all files and not call scp_to' do logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ 'tmp', 'target', {:ignore => ['tests', 'tests2']} ] expect( logger ).to receive(:trace) expect( host ).to receive( :mkdir_p ).exactly(0).times expect( conn ).to receive(:scp_to).exactly(0).times host.do_scp_to *args end it 'can take an ignore list that excludes a single file and scp the rest' do exclude_file = '07_InstallCACerts.rb' logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ 'tmp', 'target', {:ignore => [exclude_file], :dry_run => false} ] allow( Dir ).to receive( :glob ).and_return( @fileset1 + @fileset2 ) expect( logger ).to receive(:trace) expect( host ).to receive( :mkdir_p ).with('target/tmp/tests') expect( host ).to receive( :mkdir_p ).with('target/tmp/tests2') (@fileset1 + @fileset2).each do |file| if file !~ /#{exclude_file}/ file_args = [ file, File.join('target', File.dirname(file)), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to receive(:scp_to).with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) else file_args = [ file, File.join('target', File.dirname(file)), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to_not receive(:scp_to).with( *conn_args ) end end allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.do_scp_to *args end it 'can take an ignore list that excludes a dir and scp the rest' do exclude_file = 'tests' logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ 'tmp', 'target', {:ignore => [exclude_file], :dry_run => false} ] allow( Dir ).to receive( :glob ).and_return( @fileset1 + @fileset2 ) expect( logger ).to receive(:trace) expect( host ).to_not receive( :mkdir_p ).with('target/tmp/tests') expect( host ).to receive( :mkdir_p ).with('target/tmp/tests2') (@fileset1).each do |file| file_args = [ file, File.join('target', File.dirname(file)), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to_not receive(:scp_to).with( *conn_args ) end (@fileset2).each do |file| file_args = [ file, File.join('target', File.dirname(file)), {:ignore => [exclude_file], :dry_run => false} ] conn_args = file_args expect( conn ).to receive(:scp_to).with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) end allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.do_scp_to *args end end end context 'do_scp_from' do it 'do_scp_from logs info and proxies to the connection' do logger = host[:logger] conn = double(:connection) @options = { :logger => logger } host.instance_variable_set :@connection, conn args = [ 'source', 'target', {} ] conn_args = args expect( logger ).to receive(:debug) expect( conn ).to receive(:scp_from).with( *conn_args ).and_return(Beaker::Result.new(host, 'output!')) allow( conn ).to receive(:ip).and_return(host['ip']) allow( conn ).to receive(:vmhostname).and_return(host['vmhostname']) allow( conn ).to receive(:hostname).and_return(host.name) host.do_scp_from *args end end context 'do_rsync_to' do it 'do_rsync_to logs info and call Rsync class' do create_files(['source']) logger = host[:logger] @options = { :logger => logger } args = [ 'source', 'target', {:ignore => ['.bundle']} ] key = host['ssh']['keys'].first expect( File ).to receive( :exist? ).with( key ).and_return true rsync_args = [ 'source', 'target', ['-az', "-e \"ssh -i #{key} -p 22 -o 'StrictHostKeyChecking no'\"", "--exclude '.bundle'"] ] expect( host ).to receive(:reachable_name).and_return('default.ip.address') expect( Rsync ).to receive(:run).with( *rsync_args ).and_return(Rsync::Result.new('raw rsync output', 0)) host.do_rsync_to *args expect(Rsync.host).to eq('root@default.ip.address') end it 'throws an IOError when the file given doesn\'t exist' do expect { host.do_rsync_to "/does/not/exist", "does/not/exist/over/there", {} }.to raise_error(IOError) end it 'uses the ssh config file' do @options = {'ssh' => {:config => '/var/folders/v0/centos-64-x6420150625-48025-lu3u86'}} create_files(['source']) args = [ 'source', 'target', {:ignore => ['.bundle']} ] # since were using fakefs we need to create the file and directories FileUtils.mkdir_p('/var/folders/v0/') FileUtils.touch('/var/folders/v0/centos-64-x6420150625-48025-lu3u86') rsync_args = [ 'source', 'target', ['-az', "-e \"ssh -F /var/folders/v0/centos-64-x6420150625-48025-lu3u86 -o 'StrictHostKeyChecking no'\"", "--exclude '.bundle'"] ] expect(Rsync).to receive(:run).with(*rsync_args).and_return(Rsync::Result.new('raw rsync output', 0)) expect(host.do_rsync_to(*args).success?).to eq(true) end it 'does not use the ssh config file when config does not exist' do @options = {'ssh' => {:config => '/var/folders/v0/centos-64-x6420150625-48025-lu3u86'}} create_files(['source']) args = [ 'source', 'target', {:ignore => ['.bundle']} ] rsync_args = [ 'source', 'target', ['-az', "-e \"ssh -o 'StrictHostKeyChecking no'\"", "--exclude '.bundle'"] ] expect(Rsync).to receive(:run).with(*rsync_args).and_return(Rsync::Result.new('raw rsync output', 0)) expect(host.do_rsync_to(*args).success?).to eq(true) end it "doesn't corrupt :ignore option" do create_files(['source']) ignore_list = ['.bundle'] args = ['source', 'target', {:ignore => ignore_list}] key = host['ssh']['keys'].first expect( File ).to receive( :exist? ).with( key ).twice.and_return true rsync_args = ['source', 'target', ['-az', "-e \"ssh -i #{key} -p 22 -o 'StrictHostKeyChecking no'\"", "--exclude '.bundle'"]] expect(Rsync).to receive(:run).twice.with(*rsync_args).and_return(Rsync::Result.new('raw rsync output', 0)) host.do_rsync_to *args host.do_rsync_to *args end end it 'interpolates to its "name"' do expect( "#{host}" ).to be === 'name' end describe 'host close' do context 'with a nil connection object' do before do conn = nil host.instance_variable_set :@connection, conn allow(host).to receive(:close).and_call_original end it 'does not raise an error' do expect { host.close }.to_not raise_error end end end describe '#get_public_ip' do let (:aws) { double('AWSmock')} it 'calls upon the ec2 instance to get the ip address' do host.host_hash[:hypervisor] = 'ec2' host.host_hash[:instance] = aws expect(aws).to receive(:ip_address) host.get_public_ip end it 'call upon openstack host to get the ip address' do host.host_hash[:hypervisor] = 'openstack' expect(host.get_public_ip).to be(host.host_hash[:ip]) end it 'returns nil when no matching hypervisor is found' do host.host_hash[:hypervisor] = 'vmpooler' expect(host.get_public_ip).to be(nil) end it 'calls execute with curl if the host_hash[:instance] is not defined for ec2 and the host is not an instance of Windows::Host' do host.host_hash[:hypervisor] = 'ec2' host.host_hash[:instance] = nil expect(host).to receive(:instance_of?).with(Windows::Host).and_return(false) expect(host).to receive(:execute).with("curl http://169.254.169.254/latest/meta-data/public-ipv4").and_return('127.0.0.1') host.get_public_ip end it 'calls execute with wget if the host_hash[:instance] is not defined for ec2 and the host is an instance of Windows::Host' do host.host_hash[:hypervisor] = 'ec2' host.host_hash[:instance] = nil expect(host).to receive(:instance_of?).with(Windows::Host).and_return(true) expect(host).to receive(:execute).with("wget http://169.254.169.254/latest/meta-data/public-ipv4").and_return('127.0.0.1') host.get_public_ip end it 'calls execute with curl if the host_hash[:ip] is not defined for openstack and the host is not an instance of Windows::Host' do host.host_hash[:hypervisor] = 'openstack' host.host_hash[:ip] = nil expect(host).to receive(:instance_of?).with(Windows::Host).and_return(false) expect(host).to receive(:execute).with("curl http://169.254.169.254/latest/meta-data/public-ipv4").and_return('127.0.0.1') host.get_public_ip end it 'calls execute with wget if the host_hash[:ip] is not defined for openstack and the host is an instance of Windows::Host' do host.host_hash[:hypervisor] = 'openstack' host.host_hash[:ip] = nil expect(host).to receive(:instance_of?).with(Windows::Host).and_return(true) expect(host).to receive(:execute).with("wget http://169.254.169.254/latest/meta-data/public-ipv4").and_return('127.0.0.1') host.get_public_ip end end describe '#ip' do it 'calls #get_ip when get_public_ip returns nil' do allow( host ).to receive(:get_public_ip).and_return(nil) expect(host).to receive(:get_ip).and_return('127.0.0.2') expect(host.ip).to eq('127.0.0.2') end it 'does not call get_ip when #get_public_ip returns an address' do allow( host ).to receive(:get_public_ip).and_return('127.0.0.1') expect(host).to_not receive(:get_ip) expect(host.ip).to eq('127.0.0.1') end end describe "#wait_for_port" do it 'returns true when port is open' do allow(host).to receive(:repeat_fibonacci_style_for).and_return(true) expect(host.wait_for_port(22, 0)).to be true end it 'returns false when port is not open' do allow(host).to receive(:repeat_fibonacci_style_for).and_return(false) expect(host.wait_for_port(22, 0)).to be false end end describe "#fips_mode?" do it 'returns false on non-el7 hosts' do @platform = 'windows' expect(host.fips_mode?).to be false end it 'returns true when the `fips_enabled` file is present and contains "1"' do @platform = 'el-7' expect(host).to receive(:execute).with("cat /proc/sys/crypto/fips_enabled").and_return("1") expect(host.fips_mode?).to be true end it 'returns false when the `fips_enabled` file is present and contains "0"' do @platform = 'el-7' expect(host).to receive(:execute).with("cat /proc/sys/crypto/fips_enabled").and_return("0") expect(host.fips_mode?).to be false end end end end beaker-4.30.0/spec/beaker/hypervisor/000077500000000000000000000000001407603575700174425ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/hypervisor/hypervisor_spec.rb000066400000000000000000000156211407603575700232200ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Hypervisor do let( :hosts ) { make_hosts( { :platform => 'el-5' } ) } context "#create" do let( :hypervisor ) { Beaker::Hypervisor } it "includes custom hypervisor and call set_ssh_connection_preference" do allow(hypervisor).to receive(:set_ssh_connection_preference).with([], hypervisor) expect{ hypervisor.create('custom_hypervisor', [], make_opts() )}.to raise_error(LoadError, "cannot load such file -- beaker/hypervisor/custom_hypervisor") end it "sets ssh connection preference if connection_preference method is not overwritten" do hypervisor.create('none', hosts, make_opts()) expect(hosts[0][:ssh_connection_preference]).to eq([:ip,:vmhostname,:hostname]) end it "concats overriding connection_preference array with the default connection_preference" do allow(hypervisor).to receive(:connection_preference).and_return([:my,:invalid,:method_name]) hypervisor.set_ssh_connection_preference(hosts, hypervisor) expect(hosts[0][:ssh_connection_preference]).to eq([:my,:invalid,:method_name,:ip,:vmhostname,:hostname]) end it "removes unique elements from concated array while preserving order of overriding methods" do allow(hypervisor).to receive(:connection_preference).and_return([:my,:ip,:vmhostname,:method_name]) hypervisor.set_ssh_connection_preference(hosts, hypervisor) expect(hosts[0][:ssh_connection_preference]).to eq([:my,:ip,:vmhostname,:method_name,:hostname]) end it "gives highest precedence to preference specified in host file followed by hypervisor" do hosts[0].options[:ssh_preference] = [:set, :in, :hostfile] hypervisor.create('none', hosts, make_opts()) allow(hypervisor).to receive(:connection_preference).and_return([:hypervisor, :pref]) hypervisor.set_ssh_connection_preference(hosts, hypervisor) expect(hosts[0][:ssh_connection_preference]).to eq([:set, :in, :hostfile, :hypervisor, :pref, :ip, :vmhostname, :hostname]) end end context "#configure" do let( :options ) { make_opts.merge({ 'logger' => double().as_null_object }) } let( :hypervisor ) { Beaker::Hypervisor.new( hosts, options ) } context 'if :timesync option set true on host' do it 'does call timesync for host' do hosts[0].options[:timesync] = true allow( hypervisor ).to receive( :set_env ) expect( hypervisor ).to receive( :timesync ).once hypervisor.configure end it 'catches signal exceptions and returns stack trace' do logger = double() hosts[0].options[:timesync] = true allow( logger ).to receive( :error ) allow( logger ).to receive( :pretty_backtrace ).and_return("multiline\nstring") hypervisor.instance_variable_set(:@logger, logger) allow(Beaker::Command).to receive(:new).and_raise(SignalException.new('SIGTERM')) expect{ hypervisor.configure }.to raise_error(SignalException) end end context 'if :timesync option set true but false on host' do it 'does not call timesync for host' do options[:timesync] = true hosts[0].options[:timesync] = false allow( hypervisor ).to receive( :set_env ) expect( hypervisor ).to_not receive( :timesync ) hypervisor.configure end end context 'if :run_in_parallel option includes configure' do it 'timesync is run in parallel' do InParallel::InParallelExecutor.logger = logger # Need to deactivate FakeFS since the child processes write STDOUT to file. FakeFS.deactivate! hosts[0].options[:timesync] = true hosts[1].options[:timesync] = true hosts[2].options[:timesync] = true options[:run_in_parallel] = ['configure'] allow( hypervisor ).to receive( :set_env ) # This will only get hit if forking processes is supported and at least 2 items are being submitted to run in parallel expect( InParallel::InParallelExecutor ).to receive(:_execute_in_parallel).with(any_args).and_call_original.exactly(3).times hypervisor.configure end end context "if :disable_iptables option set false" do it "does not call disable_iptables" do options[:disable_iptables] = false allow( hypervisor ).to receive( :set_env ) expect( hypervisor ).to receive( :disable_iptables ).never hypervisor.configure end end context "if :disable_iptables option set true" do it "calls disable_iptables once" do options[:disable_iptables] = true allow( hypervisor ).to receive( :set_env ) expect( hypervisor ).to receive( :disable_iptables ).exactly( 1 ).times hypervisor.configure end end context "if :disable_updates option set true" do it "calls disable_updates" do options[:disable_updates] = true allow( hypervisor ).to receive( :set_env ) expect( hypervisor ).to receive( :disable_updates ).once hypervisor.configure end end context "if :disable_updates option set false" do it "does not call disable_updates_puppetlabs_com" do options[:disable_updates] = false allow( hypervisor ).to receive( :set_env ) expect( hypervisor ).to receive( :disable_updates ).never hypervisor.configure end end context 'if :configure option set false' do it 'does not make any configure calls' do options[:configure] = false options[:timesync] = true options[:root_keys] = true options[:add_el_extras] = true options[:disable_iptables] = true options[:host_name_prefix] = "test-" expect( hypervisor ).to_not receive( :timesync ) expect( hypervisor ).to_not receive( :sync_root_keys ) expect( hypervisor ).to_not receive( :add_el_extras ) expect( hypervisor ).to_not receive( :disable_iptables ) expect( hypervisor ).to_not receive( :set_env ) expect( hypervisor ).to_not receive( :host_name_prefix ) hypervisor.configure end end context 'if :configure option set true' do it 'does call set_env' do options[:configure] = true expect( hypervisor ).to receive( :set_env ).once hypervisor.configure end end context 'if :host_name_prefix is set' do it "generates hostname with prefix" do prefix = "testing-prefix-to-test-" options[:host_name_prefix] = prefix expect( hypervisor.generate_host_name().start_with?(prefix) ).to be true expect( hypervisor.generate_host_name().length - prefix.length >= 15 ).to be true end end end end end beaker-4.30.0/spec/beaker/localhost_connection_spec.rb000066400000000000000000000070331407603575700230010ustar00rootroot00000000000000require 'spec_helper' require 'net/ssh' module Beaker describe LocalConnection do let( :options ) { { :logger => double('logger').as_null_object, :ssh_env_file => '/path/to/ssh/file'} } subject(:connection) { LocalConnection.new(options) } before :each do allow( subject ).to receive(:sleep) end describe '#self.connect' do it 'loggs message' do expect(options[:logger]).to receive(:debug).with('Local connection, no connection to start') connection_constructor = LocalConnection.connect(options) expect( connection_constructor ).to be_a_kind_of LocalConnection end end describe '#close' do it 'logs message' do expect(options[:logger]).to receive(:debug).with('Local connection, no connection to close') connection.close end end describe '#with_env' do it 'sets envs temporarily' do connection.connect connection.with_env({'my_env' => 'my_env_value'}) do expect(ENV.to_hash).to include({'my_env' => 'my_env_value'}) end expect(ENV.to_hash).not_to include({'my_env' => 'my_env_value'}) end end describe '#execute' do it 'calls open3' do expect( Open3 ).to receive( :capture3 ).with({}, 'my_command') connection.connect expect(connection.execute('my_command')).to be_a_kind_of Result end it 'sets stdout, stderr and exitcode' do allow(Open3).to receive(:capture3).and_return(['stdout', 'stderr', double({exitstatus: 0})]) connection.connect result = connection.execute('my_command') expect(result.exit_code).to eq(0) expect(result.stdout).to eq('stdout') expect(result.stderr).to eq('stderr') end it 'sets logger last_result' do allow(Open3).to receive(:capture3).and_return(['stdout', 'stderr', double({exitstatus: 0})]) expect(options[:logger]).to receive(:last_result=).with(an_instance_of(Result)) connection.connect connection.execute('my_command') end it 'sets exitcode to 1, when Open3 raises exeception' do allow(Open3).to receive(:capture3).and_raise Errno::ENOENT connection.connect result = connection.execute('my_failing_command') expect(result.exit_code).to eq(1) end end describe '#scp_to' do let(:source) { '/source/path' } let(:dest) { '/dest/path' } it 'calls FileUtils.cp_r' do connection.connect expect(FileUtils).to receive(:cp_r).with(source, dest) connection.scp_to(source, dest) end it 'returns and Result object' do expect(FileUtils).to receive(:cp_r).and_return(true) connection.connect result = connection.scp_to(source, dest) expect(result.exit_code).to eq(0) expect(result.stdout).to eq(" CP'ed file #{source} to #{dest}") end it 'catches exception and logs warning message' do allow(FileUtils).to receive(:cp_r).and_raise Errno::ENOENT expect(options[:logger]).to receive(:warn).with("Errno::ENOENT error in cp'ing. Forcing the connection to close, which should raise an error.") connection.connect connection.scp_to(source, dest) end end describe '#scp_from' do let(:source) { '/source/path' } let(:dest) { '/dest/path' } it 'callse scp_to with reversed params' do expect(connection).to receive(:scp_to).with(dest, source, {}) connection.connect connection.scp_from(source, dest) end end end end beaker-4.30.0/spec/beaker/logger_junit_spec.rb000066400000000000000000000060121407603575700212560ustar00rootroot00000000000000# encoding: UTF-8 require 'spec_helper' module Beaker describe LoggerJunit do let( :xml_file ) { '/fake/file/location1' } let( :stylesheet ) { '/fake/file/location2' } describe '#is_valid_xml' do it 'rejects all invalid values' do invalid_values = [0x8, 0x10, 0xB, 0x0019, 0xD800, 0xDFFF, 0xFFFE, 0x99999, 0x110000] invalid_values.each do |value| expect( LoggerJunit.is_valid_xml(value) ).to be === false end end it 'accepts valid values' do valid_values = [0x9, 0xA, 0x0020, 0xD7FF, 0xE000, 0xFFFD, 0x100000, 0x10FFFF] valid_values.each do |value| expect( LoggerJunit.is_valid_xml(value) ).to be === true end end end describe '#escape_invalid_xml_chars' do it 'escapes invalid xml characters correctly' do testing_string = 'pants' testing_string << 0x8 expect( LoggerJunit.escape_invalid_xml_chars(testing_string) ).to be === 'pants\8' end it 'leaves a string of all valid xml characters alone' do testing_string = 'pants man, pants!' expect( LoggerJunit.escape_invalid_xml_chars(testing_string) ).to be === testing_string end end describe '#copy_stylesheet_into_xml_dir' do it 'copies the stylesheet into the correct location' do allow( File ).to receive( :file? ) { false } correct_location = File.join(File.dirname(xml_file), File.basename(stylesheet)) expect( FileUtils ).to receive( :copy ).with( stylesheet, correct_location ) LoggerJunit.copy_stylesheet_into_xml_dir(stylesheet, xml_file) end it 'skips action if the file doesn\'t exist' do allow( File ).to receive( :file? ) { true } expect( FileUtils ).not_to receive( :copy ) LoggerJunit.copy_stylesheet_into_xml_dir(stylesheet, xml_file) end end describe '#finish' do it 'opens the given file for writing, and writes the doc to it' do mock_doc = Object.new doc_xml = 'flibbity-floo' allow( mock_doc ).to receive( :write ).with(File, 2) expect( File ).to receive( :open ).with( xml_file, 'w' ) LoggerJunit.finish(mock_doc, xml_file) end end describe '#write_xml' do it 'throws an error with 1-arity in the given block' do allow( LoggerJunit ).to receive( :get_xml_contents ) expect{ LoggerJunit.write_xml(xml_file, stylesheet) do |hey| end }.to raise_error(ArgumentError) end it 'doesn\'t throw an error with 2-arity in the given block' do allow( LoggerJunit ).to receive( :get_xml_contents ) allow( LoggerJunit ).to receive( :finish ) expect{ LoggerJunit.write_xml(xml_file, stylesheet) do |hey1, hey2| end }.not_to raise_error end it 'throws an error with 3-arity in the given block' do allow( LoggerJunit ).to receive( :get_xml_contents ) expect{ LoggerJunit.write_xml(xml_file, stylesheet) do |hey1, hey2, hey3| end }.to raise_error(ArgumentError) end end end end beaker-4.30.0/spec/beaker/logger_spec.rb000066400000000000000000000361171407603575700200560ustar00rootroot00000000000000# encoding: UTF-8 require 'spec_helper' module Beaker describe Logger do let(:my_io) { StringIO.new } let(:logger) { Logger.new(my_io, :quiet => true) } let(:basic_logger) { Logger.new(:quiet => true) } let(:test_dir) { 'tmp/tests' } let(:dummy_prefix) { 'dummy' } context '#convert' do let(:valid_utf8) { "/etc/puppet/modules\n├── jimmy-appleseed (\e[0;36mv1.1.0\e[0m)\n├── jimmy-crakorn (\e[0;36mv0.4.0\e[0m)\n└── jimmy-thelock (\e[0;36mv1.0.0\e[0m)\n" } let(:invalid_utf8) {"/etc/puppet/modules\n├── jimmy-appleseed (\e[0;36mv1.1.0\e[0m)\n├── jimmy-crakorn (\e[0;36mv0.4.0\e[0m)\n└── jimmy-thelock (\e[0;36mv1.0.0\e[0m)\xAD\n"} it 'preserves valid utf-8 strings' do expect( logger.convert(valid_utf8) ).to be === valid_utf8 end it 'strips out invalid utf-8 characters' do #this is 1.9 behavior only if RUBY_VERSION.to_f >= 1.9 expect( logger.convert(invalid_utf8) ).to be === valid_utf8 else pending "not supported in ruby 1.8 (using #{RUBY_VERSION})" end end it 'supports frozen strings' do valid_utf8.freeze expect( logger.convert(valid_utf8) ).to be === valid_utf8 end end context '#generate_dated_log_folder' do it 'generates path for a given timestamp' do input_time = Time.new(2014, 6, 2, 16, 31, 22, '-07:00') expect( Logger.generate_dated_log_folder(test_dir, dummy_prefix, input_time) ).to be === File.join(test_dir, dummy_prefix, '2014-06-02_16_31_22') end it 'generates directory for a given timestamp' do input_time = Time.new(2011, 6, 10, 13, 7, 55, '-09:00') expect( File.directory? Logger.generate_dated_log_folder(test_dir, dummy_prefix, input_time) ).to be_truthy end it 'generates nested directories if given as a log_prefix' do input_time = Time.new(2011, 6, 10, 13, 7, 55, '-09:00') prefix = 'a/man/a/plan/a/canal/panama' expect( File.directory? Logger.generate_dated_log_folder(test_dir, prefix, input_time) ).to be_truthy end end context '#prefix_log_line' do around :each do |example| logger.line_prefix = '' begin example.run ensure logger.line_prefix = '' end end def prefix_log_line_test_compare_helper(in_test, out_answer) logger.with_indent do expect( logger.prefix_log_line(in_test) ).to be === out_answer end end it 'can be successfully called with a arrays' do line_arg = ['who done that', 'who wears da pants'] answer_list = line_arg.map { |item| " " + item } prefix_log_line_test_compare_helper(line_arg, answer_list) end it 'removes carriage returns' do line_arg = " \r\n god doing this sucked" answer = " \n god doing this sucked" prefix_log_line_test_compare_helper(line_arg, answer) end it 'includes a newline at the end if it was on the input' do line_arg = "why should this matter\n" answer = " why should this matter\n" prefix_log_line_test_compare_helper(line_arg, answer) end it 'prepends multiple lines in one string' do line_arg = "\n\nwhy should this matter\n" answer = " \n \n why should this matter\n" prefix_log_line_test_compare_helper(line_arg, answer) end it 'can be nested' do line_arg = "\n\nwhy should this matter" answer = " \n \n why should this matter" logger.with_indent do logger.with_indent do logger.with_indent do expect( logger.prefix_log_line(line_arg) ).to be === answer end end end end end context 'when indenting' do around :each do |example| logger.line_prefix = '' begin example.run ensure logger.line_prefix = '' end end it 'steps in correctly (simple case)' do logger.with_indent do expect( logger.line_prefix ).to be === ' ' end end it 'sets length correctly in mixed scenario ' do logger.with_indent do logger.with_indent {} logger.with_indent do logger.with_indent {} expect( logger.line_prefix ).to be === ' ' end end end it 'can handle arbitrary strings as prefixes' do logger.line_prefix = 'Some string:' expect( logger.line_prefix ).to be === 'Some string:' end it 'can handle stepping in with arbitrary strings' do logger.line_prefix = 'Some string:' logger.with_indent do logger.with_indent do expect( logger.line_prefix ).to be === 'Some string: ' end end end it 'can handle stepping in and out with arbitrary strings' do logger.line_prefix = 'Some string:' 10.times { logger.with_indent {} } expect( logger.line_prefix ).to be === 'Some string:' end it 'restores the original prefix if an argument is raised' do logger.line_prefix = 'Some string:' expect do logger.with_indent do raise "whoops" end end.to raise_error(RuntimeError, 'whoops') expect(logger.line_prefix).to eq('Some string:') end end context 'new' do it 'does not duplicate STDOUT when directly passed to it' do stdout_logger = Logger.new STDOUT expect( stdout_logger.destinations.size ).to be === 1 end context 'default for' do its(:destinations) { should include(STDOUT) } its(:color) { should be_nil } its(:log_level) { should be :verbose } end context 'log_colors' do original_build_number = ENV['BUILD_NUMBER'] before :each do ENV['BUILD_NUMBER'] = nil end after :each do ENV['BUILD_NUMER'] = original_build_number end it 'should have the default log_colors' do expect(logger.log_colors).to be == { :error=> Beaker::Logger::RED, :warn=> Beaker::Logger::BRIGHT_RED, :success=> Beaker::Logger::MAGENTA, :notify=> Beaker::Logger::BLUE, :info=> Beaker::Logger::GREEN, :debug=> Beaker::Logger::WHITE, :trace=> Beaker::Logger::BRIGHT_YELLOW, :perf=> Beaker::Logger::BRIGHT_MAGENTA, :host=> Beaker::Logger::YELLOW } end context 'when passing in log_color options' do let(:log_colors) { { :error => "\e[00;30m" } } let(:logger) { Logger.new(my_io, :quiet => true, :log_colors => log_colors) } it 'should override the specified log colors' do expect(logger.log_colors[:error]).to be == Beaker::Logger::BLACK end it 'should leave other colors as the default' do expect(logger.log_colors[:warn]).to be == Beaker::Logger::BRIGHT_RED end end context 'with CI detected' do before :each do ENV['BUILD_NUMBER'] = 'bob' end context 'when using the default log colors' do it 'should override notify with NORMAL' do expect(logger.log_colors[:notify]).to be == Beaker::Logger::NORMAL end it 'should override info with NORMAL' do expect(logger.log_colors[:info]).to be == Beaker::Logger::NORMAL end end context 'when overriding default log colors' do let(:log_colors) { { :error => "\e[00;30m" } } let(:logger) { Logger.new(my_io, :quiet => true, :log_colors => log_colors) } it 'should override the specified log colors' do expect(logger.log_colors[:error]).to be == Beaker::Logger::BLACK end it 'should not override notify with NORMAL' do expect(logger.log_colors[:notify]).not_to be == Beaker::Logger::NORMAL end it 'should not override info with NORMAL' do expect(logger.log_colors[:notify]).not_to be == Beaker::Logger::NORMAL end end end end end context 'it can' do it 'open/create a file when a string is given to add_destination' do logger.add_destination 'my_tmp_file' expect( File.exists?( 'my_tmp_file' ) ).to be_truthy io = logger.destinations.select {|d| d.respond_to? :path }.first expect( io.path ).to match /my_tmp_file/ end it 'remove destinations with the remove_destinations method' do logger.add_destination 'my_file' logger.remove_destination my_io logger.remove_destination 'my_file' expect( logger.destinations ).to be_empty end it 'strip colors from arrays of input' do stripped = logger.strip_colors_from [ "\e[00;30m text! \e[00;00m" ] expect( stripped ).to be === [ ' text! ' ] end it 'colors strings if @color is set' do colorized_logger = Logger.new my_io, :color => true, :quiet => true expect( my_io ).to receive( :print ).with "\e[00;30m" expect( my_io ).to receive( :print ) expect( my_io ).to receive( :puts ).with 'my string' colorized_logger.optionally_color "\e[00;30m", 'my string' end context 'at trace log_level' do subject( :trace_logger ) { Logger.new( my_io, :log_level => 'trace', :quiet => true, :color => true ) } its( :is_debug? ) { should be_truthy } its( :is_trace? ) { should be_truthy } its( :is_warn? ) { should be_truthy } context 'but print' do before do allow( my_io ).to receive :puts expect( my_io ).to receive( :print ).at_least :twice end it( 'warnings' ) { trace_logger.warn 'IMA WARNING!' } it( 'successes' ) { trace_logger.success 'SUCCESS!' } it( 'errors' ) { trace_logger.error 'ERROR!' } it( 'host_output' ) { trace_logger.host_output 'ERROR!' } it( 'debugs' ) { trace_logger.debug 'DEBUGGING!' } it( 'traces' ) { trace_logger.trace 'TRACING!' } end end context 'at verbose log_level' do subject( :verbose_logger ) { Logger.new( my_io, :log_level => 'verbose', :quiet => true, :color => true ) } its( :is_trace? ) { should be_falsy } its( :is_debug? ) { should be_falsy } its( :is_verbose? ) { should be_truthy } its( :is_warn? ) { should be_truthy } context 'but print' do before do allow( my_io ).to receive :puts expect( my_io ).to receive( :print ).at_least :twice end it( 'warnings' ) { verbose_logger.warn 'IMA WARNING!' } it( 'successes' ) { verbose_logger.success 'SUCCESS!' } it( 'errors' ) { verbose_logger.error 'ERROR!' } it( 'host_output' ) { verbose_logger.host_output 'ERROR!' } it( 'debugs' ) { verbose_logger.debug 'NOT DEBUGGING!' } end end context 'at debug log_level' do subject( :debug_logger ) { Logger.new( my_io, :log_level => 'debug', :quiet => true, :color => true ) } its( :is_trace? ) { should be_falsy } its( :is_debug? ) { should be_truthy } its( :is_warn? ) { should be_truthy } context 'successfully print' do before do allow( my_io ).to receive :puts expect( my_io ).to receive( :print ).at_least :twice end it( 'warnings' ) { debug_logger.warn 'IMA WARNING!' } it( 'debugs' ) { debug_logger.debug 'IMA DEBUGGING!' } it( 'successes' ) { debug_logger.success 'SUCCESS!' } it( 'errors' ) { debug_logger.error 'ERROR!' } it( 'host_output' ) { debug_logger.host_output 'ERROR!' } end end context 'at info log_level' do subject( :info_logger ) { Logger.new( my_io, :log_level => :info, :quiet => true, :color => true ) } its( :is_debug? ) { should be_falsy } its( :is_trace? ) { should be_falsy } context 'skip' do before do expect( my_io ).to_not receive :puts expect( my_io ).to_not receive :print end it( 'debugs' ) { info_logger.debug 'NOT DEBUGGING!' } it( 'traces' ) { info_logger.debug 'NOT TRACING!' } end context 'but print' do before do expect( my_io ).to receive :puts expect( my_io ).to receive( :print ).twice end it( 'successes' ) { info_logger.success 'SUCCESS!' } it( 'notifications' ) { info_logger.notify 'NOTFIY!' } it( 'errors' ) { info_logger.error 'ERROR!' } end end context 'SUT output logging' do context 'host output logging' do subject( :host_output ) { Logger.new( my_io, :log_level => :verbose, :quiet => true, :color => true )} it 'should output GREY when @color is set to true' do colorized_logger = host_output expect( my_io ).to receive( :print ).with "\e[01;30m" expect( my_io ).to receive( :print ) expect( my_io ).to receive( :puts ).with 'my string' colorized_logger.optionally_color "\e[01;30m", 'my string' end end context 'color host output' do subject( :color_host_output ) { Logger.new( my_io, :log_level => :verbose, :quiet => true, :color => true )} it 'colors host_output' do colorized_logger = color_host_output expect( my_io ).to receive( :print ).with "" expect( my_io ).to receive( :puts ).with 'my string' colorized_logger.optionally_color "", 'my string' end end end end end end beaker-4.30.0/spec/beaker/network_manager_spec.rb000066400000000000000000000056331407603575700217610ustar00rootroot00000000000000# encoding: UTF-8 require 'spec_helper' module Beaker describe NetworkManager do let( :mock_provisioning_logger ) { mock_provisioning_logger = Object.new allow( mock_provisioning_logger ).to receive( :notify ) mock_provisioning_logger } let( :options ) { make_opts.merge({ 'logger' => double().as_null_object, :logger_sut => mock_provisioning_logger, :log_prefix => @log_prefix, :hosts_file => @hosts_file, :default_log_prefix => 'hello_default', }) } let( :network_manager ) { NetworkManager.new(options, options[:logger]) } let( :hosts ) { make_hosts } let( :host ) { hosts[0] } describe '#log_sut_event' do before :each do @log_prefix = 'log_prefix_dummy' @hosts_file = 'dummy_hosts' end it 'creates the correct content for an event' do log_line = network_manager.log_sut_event host, true pieces = log_line.split("\t") hypervisor_value = host['hypervisor'] ? host['hypervisor'] : '' platform_value = host['platform'] ? host['platform'] : '' expect( pieces[1] ).to be === '[+]' expect( pieces[2] ).to be === hypervisor_value expect( pieces[3] ).to be === platform_value expect( pieces[4] ).to be === host.log_prefix end it 'follows the create parameter correctly' do log_line = network_manager.log_sut_event host, true pieces = log_line.split("\t") expect( pieces[1] ).to be === '[+]' log_line = network_manager.log_sut_event host, false pieces = log_line.split("\t") expect( pieces[1] ).to be === '[-]' end it 'sends the log line to the provisioning logger' do nm = network_manager options[:logger_sut] = mock_provisioning_logger expect( mock_provisioning_logger ).to receive( :notify ).once nm.log_sut_event host, true end it 'throws an error if the provisioning logger hasn\'t been created yet' do nm = network_manager options.delete(:logger_sut) expect{ nm.log_sut_event(host, true) }.to raise_error(ArgumentError) end end it 'uses user defined log prefix if it is provided' do @log_prefix = 'dummy_log_prefix' @hosts_file = 'dummy_hosts' nm = network_manager cur_prefix = options[:log_prefix] expect(cur_prefix).to be === @log_prefix end it 'uses host based log prefix, when there is not user defined log prefix' do @log_prefix = nil @hosts_file = 'dummy_hosts' nm = network_manager cur_prefix = options[:log_prefix] expect(cur_prefix).to be === @hosts_file end it 'uses default log prefix, when there is no user defined and no host file' do @log_prefix = nil @hosts_file = nil nm = network_manager cur_prefix = options[:log_prefix] expect(cur_prefix).to be === 'hello_default' end end end beaker-4.30.0/spec/beaker/options/000077500000000000000000000000001407603575700167235ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/options/command_line_parser_spec.rb000066400000000000000000000102531407603575700242640ustar00rootroot00000000000000require "spec_helper" module Beaker module Options describe CommandLineParser do let(:parser) {Beaker::Options::CommandLineParser.new} let(:test_opts) {["-h", "vcloud.cfg", "--debug", "--tests", "test.rb", "--help"]} let(:full_opts_in) {["--hosts", "host.cfg", "--options", "opts_file", "--helper", "path_to_helper", "--load-path", "load_path", "--tests", "test1.rb,test2.rb,test3.rb", "--pre-suite", "pre_suite.rb", "--post-suite", "post_suite.rb", "--pre-cleanup", "pre_cleanup.rb", "--no-provision", "--preserve-hosts", "always", "--root-keys", "--keyfile", "../.ssh/id_rsa", "--install", "gitrepopath", "-m", "module", "-q", "--dry-run", "--no-ntp", "--repo-proxy", "--add-el-extras", "--config", "anotherfile.cfg", "--fail-mode", "fast", "--no-color", "--no-color-host-output", "--version", "--log-level", "info", "--package-proxy", "http://192.168.100.1:3128", "--collect-perf-data", "--parse-only", "--validate", "--timeout", "40", "--log-prefix", "pants", "--configure", "--test-tag-and", "1,2,3", "--test-tag-or", "4,5,6", "--test-tag-exclude", "7,8,9", "--xml-time-order"]} let(:full_opts_out) {{:hosts_file=>"anotherfile.cfg",:options_file=>"opts_file", :helper => "path_to_helper", :load_path => "load_path", :tests => "test1.rb,test2.rb,test3.rb", :pre_suite => "pre_suite.rb", :post_suite => "post_suite.rb", :pre_cleanup => "pre_cleanup.rb", :provision=>false, :preserve_hosts => "always", :root_keys=>true, :keyfile => "../.ssh/id_rsa", :install => "gitrepopath", :modules=>"module", :quiet=>true, :dry_run=>true, :timesync=>false, :repo_proxy=>true, :add_el_extras=>true, :fail_mode => "fast", :color=>false, :color_host_output=>false, :beaker_version_print=>true, :log_level => "info", :package_proxy => "http://192.168.100.1:3128", :collect_perf_data=>"normal", :parse_only=>true, :validate=>true, :timeout => "40", :log_prefix => "pants", :configure => true, :test_tag_and => "1,2,3", :test_tag_or => "4,5,6", :test_tag_exclude => "7,8,9", :xml_time_enabled => true}} let(:validate_true) {["--validate"]} let(:validate_false) {["--no-validate"]} let(:configure_true) {['--configure']} let(:configure_false) {['--no-configure']} let(:provision_false) {['--no-provision']} let(:provision_other) {{:provision => false, :configure => false, :validate => false}} let(:provision_both_in) {['--no-provision', '--configure', '--validate']} let(:provision_both_out) {{:provision => false, :configure => true, :validate => true}} let(:provision_half_in) {['--no-provision', '--configure']} let(:provision_half_out) {{:provision => false, :configure => true, :validate => false}} it "can correctly read command line input" do expect(parser.parse(test_opts)).to be === {:hosts_file=>"vcloud.cfg", :log_level=>"debug", :tests=>"test.rb", :help=>true} end it "supports all our command line options" do expect(parser.parse(full_opts_in)).to be === full_opts_out end it "supports both validate options" do expect(parser.parse(validate_true)).to be === {:validate=>true} expect(parser.parse(validate_false)).to be === {:validate=>false} end it 'supports both configure options' do expect(parser.parse(configure_true)).to be === {:configure=>true} expect(parser.parse(configure_false)).to be === {:configure=>false} end it "can produce a usage description" do expect{parser.usage}.to_not raise_error end context '--no-provision flag effects other options' do it 'sets --no-validate/configure when --no-provision is set' do expect(parser.parse(provision_false)).to be === provision_other end it 'can still have --validate & --configure set correctly when --no-provision is set' do expect(parser.parse(provision_both_in)).to be === provision_both_out end it 'can override just one of the two flags when --no-provision is set' do expect(parser.parse(provision_half_in)).to be === provision_half_out end end end end end beaker-4.30.0/spec/beaker/options/data/000077500000000000000000000000001407603575700176345ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/options/data/badyaml.cfg000066400000000000000000000006211407603575700217250ustar00rootroot00000000000000HOSTS: pe-ubuntu-lucid: roles: - agent - dashboard - database - master vmname : pe-ubuntu-lucid platform: ubuntu-10.04-i386 snapshot : clean-w-keys hypervisor : fusion pe-centos6: roles: - agent vmname : pe-centos6 platform: el-6-i386 hypervisor : fusion snapshot *** clean-w-keys CONFIG: nfs_server: none consoleport: 443 beaker-4.30.0/spec/beaker/options/data/hosts.cfg000066400000000000000000000006161407603575700214600ustar00rootroot00000000000000HOSTS: pe-ubuntu-lucid: roles: - agent - dashboard - database - master vmname : pe-ubuntu-lucid platform: ubuntu-10.04-i386 snapshot : clean-w-keys hypervisor : fusion pe-centos6: roles: - agent vmname : pe-centos6 platform: el-6-i386 hypervisor : fusion snapshot: clean-w-keys CONFIG: nfs_server: none consoleport: 443 beaker-4.30.0/spec/beaker/options/data/opts.txt000066400000000000000000000002121407603575700213550ustar00rootroot00000000000000{ :debug => true, :tests => 'test.rb', :pre_suite => [ 'pre-suite.rb'], :post_suite => "post_suite1.rb,post_suite2.rb", } beaker-4.30.0/spec/beaker/options/hosts_file_parser_spec.rb000066400000000000000000000107421407603575700240010ustar00rootroot00000000000000require "spec_helper" module Beaker module Options describe HostsFileParser do let(:parser) {HostsFileParser} let(:filepath) {File.join(File.expand_path(File.dirname(__FILE__)), "data", "hosts.cfg")} describe '#parse_hosts_file' do it "can correctly read a host file" do FakeFS.deactivate! config = parser.parse_hosts_file(filepath) expect(config).to be === {:HOSTS=>{:"pe-ubuntu-lucid"=>{:roles=>["agent", "dashboard", "database", "master"], :vmname=>"pe-ubuntu-lucid", :platform=>"ubuntu-10.04-i386", :snapshot=>"clean-w-keys", :hypervisor=>"fusion"}, :"pe-centos6"=>{:roles=>["agent"], :vmname=>"pe-centos6", :platform=>"el-6-i386", :hypervisor=>"fusion", :snapshot=>"clean-w-keys"}}, :nfs_server=>"none", :consoleport=>443} end it "can merge CONFIG section into overall hash" do FakeFS.deactivate! config = parser.parse_hosts_file(filepath) expect(config['CONFIG']).to be === nil expect(config['consoleport']).to be === 443 end it "returns empty configuration when no file provided" do FakeFS.deactivate! expect(parser.parse_hosts_file()).to be === { :HOSTS => {} } end it "raises an error on no file found" do FakeFS.deactivate! expect{parser.parse_hosts_file("not a valid path")}.to raise_error(/is not a valid path/) end it "raises an error on bad yaml file" do FakeFS.deactivate! expect( File ).to receive(:exist?).and_return(true) expect { parser.parse_hosts_file("not a valid path") }.to raise_error(Errno::ENOENT) end it 'returns a #new_host_options hash if given no arguments' do host_options = parser.parse_hosts_file expect( host_options ).to be === parser.new_host_options end it 'passes a YAML.load call through to #merge_hosts_yaml' do yaml_string = 'not actually yaml, but that wont matter' expect( File ).to receive( :exist?).and_return(true) expect( File ).to receive( :read ).and_return(yaml_string) parser.parse_hosts_file( yaml_string ) end it 'processes ERB in the host YAML successfully' do yaml_string = '1 plus 2: <%= 1 + 2 %>' expect( File ).to receive( :exist?).and_return(true) expect( File ).to receive( :read ).and_return(yaml_string) beaker_options_hash = parser.parse_hosts_file( yaml_string ) expect(beaker_options_hash['1 plus 2']).to eq(3) end end describe '#parse_hosts_string' do it 'will return a #new_host_options hash if given no arguments' do host_options = parser.parse_hosts_string expect( host_options ).to be === parser.new_host_options end it 'passes a YAML.load call through to #merge_hosts_yaml' do yaml_string = 'not actually yaml, but that wont matter' expect( YAML ).to receive( :load ).with( yaml_string ) parser.parse_hosts_string( yaml_string ) end end describe '#merge_hosts_yaml' do it 'merges yielded block result with host_options argument & returns it' do host_options = {} yield_to_merge = { :pants => 'truth to the face' } block_count = 0 answer = parser.merge_hosts_yaml( host_options, 'err_msg' ) { block_count += 1 yield_to_merge } expect( block_count ).to be === 1 expect( answer ).to be === host_options.merge( yield_to_merge ) end class MockSyntaxError < Psych::SyntaxError def initialize super( '', 0, 0, 0, '', '' ) end end it 'raises an ArgumentError if can\'t process YAML' do # allow( parser ).to receive( :merge_hosts_yaml ) err_value = 'err_msg8797' expect { parser.merge_hosts_yaml( {}, err_value ) { raise MockSyntaxError } }.to raise_error( ArgumentError, /#{err_value}/ ) end end describe '#fix_roles_array' do it 'adds a roles array to a host if not present' do host_options = { 'HOSTS' => { 'host1' => {}, 'host2' => {} }} parser.fix_roles_array( host_options ) host_options['HOSTS'].each do |host_name, host_hash| expect( host_hash['roles'] ).to be === [] end end end end end end beaker-4.30.0/spec/beaker/options/options_file_parser_spec.rb000066400000000000000000000013021407603575700243240ustar00rootroot00000000000000require "spec_helper" module Beaker module Options describe OptionsFileParser do let(:parser) {Beaker::Options::OptionsFileParser} let(:simple_opts) {File.join(File.expand_path(File.dirname(__FILE__)), "data", "opts.txt")} it "can correctly read options from a file" do FakeFS.deactivate! 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"} end it "raises an error on no file found" do FakeFS.deactivate! expect{parser.parse_options_file("not a valid path")}.to raise_error(ArgumentError) end end end end beaker-4.30.0/spec/beaker/options/options_hash_spec.rb000066400000000000000000000017631407603575700227670ustar00rootroot00000000000000require "spec_helper" module Beaker module Options describe OptionsHash do let(:options) { Beaker::Options::OptionsHash.new } it "supports is_pe?, defaults to pe" do expect(options.is_pe?).to be_truthy end it "supports is_pe?, respects :type == foss" do options[:type] = 'foss' expect(options.is_pe?).to be_falsy end describe '#get_type' do let(:options) { Beaker::Options::OptionsHash.new } it 'returns pe as expected in the normal case' do newhash = options.merge({:type => 'pe'}) expect(newhash.get_type).to be === :pe end it 'returns foss as expected in the normal case' do newhash = options.merge({:type => 'foss'}) expect(newhash.get_type).to be === :foss end it 'returns foss as the default' do newhash = options.merge({:type => 'git'}) expect(newhash.get_type).to be === :foss end end end end end beaker-4.30.0/spec/beaker/options/parser_spec.rb000066400000000000000000000722721407603575700215700ustar00rootroot00000000000000require "spec_helper" module Beaker module Options describe Parser do let(:parser) { Parser.new } let(:opts_path) { File.join(File.expand_path(File.dirname(__FILE__)), "data", "opts.txt") } let(:hosts_path) { File.join(File.expand_path(File.dirname(__FILE__)), "data", "hosts.cfg") } it "supports usage function" do expect { parser.usage }.to_not raise_error end describe 'parse_git_repos' do it "transforms arguments of / to /#" do opts = ["PUPPET/3.1"] expect(parser.parse_git_repos(opts)).to be === ["#{parser.repo}/puppet.git#3.1"] end it "recognizes PROJECT_NAMEs of PUPPET, FACTER, HIERA, and HIERA-PUPPET" do projects = [['puppet', 'my_branch', 'PUPPET/my_branch'], ['facter', 'my_branch', 'FACTER/my_branch'], ['hiera', 'my_branch', 'HIERA/my_branch'], ['hiera-puppet', 'my_branch', 'HIERA-PUPPET/my_branch']] projects.each do |project, ref, input| expect(parser.parse_git_repos([input])).to be === ["#{parser.repo}/#{project}.git##{ref}"] end end end describe 'split_arg' do it "can split comma separated list into an array" do arg = "file1,file2,file3" expect(parser.split_arg(arg)).to be === ["file1", "file2", "file3"] end it "can use an existing Array as an acceptable argument" do arg = ["file1", "file2", "file3"] expect(parser.split_arg(arg)).to be === ["file1", "file2", "file3"] end it "can generate an array from a single value" do arg = "i'mjustastring" expect(parser.split_arg(arg)).to be === ["i'mjustastring"] end end context 'testing path traversing' do let(:test_dir) { 'tmp/tests' } let(:rb_test) { File.expand_path(test_dir + '/my_ruby_file.rb') } let(:pl_test) { File.expand_path(test_dir + '/my_perl_file.pl') } let(:sh_test) { File.expand_path(test_dir + '/my_shell_file.sh') } let(:rb_other) { File.expand_path(test_dir + '/other/my_other_ruby_file.rb') } it 'only collects ruby files as test files' do files = [rb_test, pl_test, sh_test, rb_other] create_files(files) expect(parser.file_list([File.expand_path(test_dir)])).to be === [rb_test, rb_other] end it 'raises an error when no ruby files are found' do files = [pl_test, sh_test] create_files(files) expect { parser.file_list([File.expand_path(test_dir)]) }.to raise_error(ArgumentError) end it 'raises an error when no paths are specified for searching' do @files = '' expect { parser.file_list('') }.to raise_error(ArgumentError) end end context 'combining split_arg and file_list maintain test file ordering' do let(:test_dir) { 'tmp/tests' } let(:other_test_dir) { 'tmp/tests2' } before :each do files = [ '00_EnvSetup.rb', '035_StopFirewall.rb', '05_HieraSetup.rb', '01_TestSetup.rb', '03_PuppetMasterSanity.rb', '06_InstallModules.rb', '02_PuppetUserAndGroup.rb', '04_ValidateSignCert.rb', '07_InstallCACerts.rb'] @lone_file = '08_foss.rb' @fileset1 = files.shuffle.map { |file| test_dir + '/' + file } @fileset2 = files.shuffle.map { |file| other_test_dir + '/' + file } @sorted_expanded_fileset1 = @fileset1.map { |f| File.expand_path(f) }.sort @sorted_expanded_fileset2 = @fileset2.map { |f| File.expand_path(f) }.sort create_files(@fileset1) create_files(@fileset2) create_files([@lone_file]) end it "when provided a file followed by dir, runs the file first" do arg = "#{@lone_file},#{test_dir}" output = parser.file_list(parser.split_arg(arg)) expect(output).to be === [@lone_file, @sorted_expanded_fileset1].flatten end it "when provided a dir followed by a file, runs the file last" do arg = "#{test_dir},#{@lone_file}" output = parser.file_list(parser.split_arg(arg)) expect(output).to be === [@sorted_expanded_fileset1, @lone_file].flatten end it "correctly orders files in a directory" do arg = "#{test_dir}" output = parser.file_list(parser.split_arg(arg)) expect(output).to be === @sorted_expanded_fileset1 end it "when provided two directories orders each directory separately" do arg = "#{test_dir}/,#{other_test_dir}/" output = parser.file_list(parser.split_arg(arg)) expect(output).to be === @sorted_expanded_fileset1 + @sorted_expanded_fileset2 end end describe '#parse_args' do before { FakeFS.deactivate! } it 'pulls the args into key called :command_line' do my_args = ['--log-level', 'debug', '-h', hosts_path] expect(parser.parse_args(my_args)[:command_line]).to include(my_args.join(' ')) expect(parser.attribution[:command_line]).to be == 'cmd' expect(parser.attribution[:hosts_file]).to be == 'cmd' expect(parser.attribution[:log_level]).to be == 'cmd' expect(parser.attribution[:pe_dir]).to be == 'preset' end describe 'does prioritization correctly' do let(:env) { @env || {:level => 'highest'} } let(:argv) { @argv || {:level => 'second'} } let(:host_file) { @host_file || {:level => 'third'} } let(:opt_file) { @opt_file || { :level => 'fourth', :ssh => { :auth_methods => 'auth123', :user_known_hosts_file => 'hosts123' } }} let(:subcommand_file) {@subcommand_file || {:level => 'fifth'}} let(:homedir_file) {@homedir_file || { :level => 'sixth', :ssh => { :auth_methods => 'auth_home_123' } }} let(:project_file) {@project_file || { :level => 'seventh', :ssh => { :auth_methods => 'auth_project_123' } }} let(:presets) { { :level => 'lowest', :ssh => { :config => 'config123', :verify_host_key => 'verify123', :port => 'port123', :forward_agent => 'forwardagent123', :keys => 'keys123', :keepalive => 'keepalive123' } }} before :each do expect(parser).to receive(:normalize_args).and_return(true) end def mock_out_parsing presets_obj = double() allow(presets_obj).to receive(:presets).and_return(presets) allow(presets_obj).to receive(:env_vars).and_return(env) parser.instance_variable_set(:@presets, presets_obj) command_line_parser_obj = double() allow(command_line_parser_obj).to receive(:parse).and_return(argv) parser.instance_variable_set(:@command_line_parser, command_line_parser_obj) allow(OptionsFileParser).to receive(:parse_options_file).and_return(opt_file) allow(parser).to receive(:parse_hosts_options).and_return(host_file) allow(SubcommandOptionsParser).to receive(:parse_options_file).with(".beaker.yml").and_return(project_file) allow(SubcommandOptionsParser).to receive(:parse_subcommand_options).with(anything, "#{ENV['HOME']}/.beaker/subcommand_options.yaml").and_return(homedir_file) allow(SubcommandOptionsParser).to receive(:parse_subcommand_options).with(anything, Pathname(".beaker/subcommand_options.yaml")).and_return(subcommand_file) end it 'presets have the lowest priority' do @env = @argv = @host_file = @opt_file = @subcommand_file = @homedir_file = @project_file = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:level]).to be == 'lowest' expect(attribution[:level]).to be == 'preset' end it 'project options should have seventh priority' do @env = @argv = @host_file = @opt_file = @subcommand_file = @homedir_file = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:ssh][:auth_methods]).to be == 'auth_project_123' expect(attribution[:ssh][:auth_methods]).to be == 'project' expect(opts[:level]).to be == 'seventh' expect(attribution[:level]).to be == 'project' end it 'home directory options should have sixth priority' do @env = @argv = @host_file = @opt_file = @subcommand_file = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:ssh][:auth_methods]).to be == 'auth_home_123' expect(attribution[:ssh][:auth_methods]).to be == 'homedir' expect(opts[:level]).to be == 'sixth' expect(attribution[:level]).to be == 'homedir' end it 'subcommand_options should have fifth priority' do @env = @argv = @host_file = @opt_file = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:level]).to be == 'fifth' expect(attribution[:level]).to be == 'subcommand' end it 'options file has fourth priority' do @env = @argv = @host_file = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(attribution[:ssh]).to be_a(Hash) expect(attribution[:ssh][:auth_methods]).to be == 'options_file' expect(attribution[:ssh][:user_known_hosts_file]).to be == 'options_file' expect(attribution[:ssh][:config]).to be == 'preset' expect(attribution[:ssh][:verify_host_key]).to be == 'preset' expect(attribution[:ssh][:port]).to be == 'preset' expect(attribution[:ssh][:forward_agent]).to be == 'preset' expect(attribution[:ssh][:keys]).to be == 'preset' expect(attribution[:ssh][:keepalive]).to be == 'preset' expect(opts[:level]).to be == 'fourth' expect(attribution[:level]).to be == 'options_file' end it 'host file CONFIG section has third priority' do @env = @argv = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:level]).to be == 'third' expect(attribution[:level]).to be == 'host_file' end it 'command line arguments have second priority' do @env = {} mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:level]).to be == 'second' expect(attribution[:level]).to be == 'cmd' end it 'env vars have highest priority' do mock_out_parsing opts = parser.parse_args([]) attribution = parser.attribution expect(opts[:level]).to be == 'highest' expect(attribution[:level]).to be == 'env' end it "loads the options file from a project file" do mock_out_parsing project_file[:options_file] = 'my_options_file.rb' allow(OptionsFileParser).to receive(:parse_options_file).with('my_options_file.rb').and_return(ssh: {config: true}) output = parser.parse_args([]) attribution = parser.attribution expect(output[:ssh][:config]).to be true expect(attribution[:ssh][:config]).to eq('options_file') end it "loads project file options with the init subcommand" do @env = @argv = @host_file = @opt_file = @subcommand_file = @homedir_file = {} mock_out_parsing output = parser.parse_args(%w[init --hosts redhat7-64ma]) attribution = parser.attribution expect(output[:level]).to eq('seventh') expect(attribution[:level]).to eq('project') end end it "can correctly combine arguments from different sources" do build_url = 'http://my.build.url/' type = 'git' log_level = 'debug' old_build_url = ENV["BUILD_URL"] ENV["BUILD_URL"] = build_url args = ["-h", hosts_path, "--log-level", log_level, "--type", type, "--install", "PUPPET/1.0,HIERA/hello"] output = parser.parse_args(args) attribution = parser.attribution expect(output[:hosts_file]).to be == hosts_path expect(attribution[:hosts_file]).to be == 'cmd' expect(output[:jenkins_build_url]).to be == build_url expect(attribution[:jenkins_build_url]).to be == 'env' expect(output[:install]).to include('git://github.com/puppetlabs/hiera.git#hello') expect(attribution[:install]).to be == 'runtime' ENV["BUILD_URL"] = old_build_url end it "ensures that fail-mode is one of fast/slow" do args = ["-h", hosts_path, "--log-level", "debug", "--fail-mode", "nope"] expect { parser.parse_args(args) }.to raise_error(ArgumentError) end end describe '#parse_hosts_options' do context 'Hosts file exists' do before :each do allow(File).to receive(:exists?).and_return(true) end it 'returns the parser\'s output' do parser.instance_variable_set( :@options, {} ) test_value = 'blaqwetjijl,emikfuj1235' allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_file ).and_return( test_value ) val1, _ = parser.parse_hosts_options expect( val1 ).to be === test_value end end context 'Hosts file does not exist' do require 'beaker-hostgenerator' before :each do allow(File).to receive(:exists?).and_return(false) end it 'calls beaker-hostgenerator to get hosts information' do parser.instance_variable_set( :@options, { :hosts_file => 'notafile.yml' } ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_file ).and_raise( Errno::ENOENT ) mock_beaker_hostgenerator_cli = Object.new cli_execute_return = 'job150865' expect( mock_beaker_hostgenerator_cli ).to receive( :execute ).and_return( cli_execute_return ) expect( BeakerHostGenerator::CLI ).to receive( :new ).with( [ 'notafile.yml' ] ).and_return( mock_beaker_hostgenerator_cli ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_string ).with( cli_execute_return ) parser.parse_hosts_options end it 'calls beaker-hostgenerator to get hosts information with a default hypervisor' do old_beaker_hypervisor = ENV['BEAKER_HYPERVISOR'] begin ENV['BEAKER_HYPERVISOR'] = 'docker' parser.instance_variable_set( :@options, { :hosts_file => 'notafile.yml' } ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_file ).and_raise( Errno::ENOENT ) mock_beaker_hostgenerator_cli = Object.new cli_execute_return = 'job150865' expect( mock_beaker_hostgenerator_cli ).to receive( :execute ).and_return( cli_execute_return ) expect( BeakerHostGenerator::CLI ).to receive( :new ).with( [ 'notafile.yml', '--hypervisor', 'docker' ] ).and_return( mock_beaker_hostgenerator_cli ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_string ).with( cli_execute_return ) parser.parse_hosts_options ensure ENV['BEAKER_HYPERVISOR'] = old_beaker_hypervisor end end it 'sets the :hosts_file_generated flag to signal others when needed' do options_test = { :hosts_file => 'not_a_file.yml' } parser.instance_variable_set( :@options, options_test ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_file ).and_raise( Errno::ENOENT ) mock_beaker_hostgenerator_cli = Object.new allow( mock_beaker_hostgenerator_cli ).to receive( :execute ) allow( BeakerHostGenerator::CLI ).to receive( :new ).and_return( mock_beaker_hostgenerator_cli ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_string ) parser.parse_hosts_options expect( options_test[:hosts_file_generated] ).to be true end it 'beaker-hostgenerator failures trigger nice prints & a rethrow' do options_test = { :hosts_file => 'not_a_file.yml' } parser.instance_variable_set( :@options, options_test ) allow( Beaker::Options::HostsFileParser ).to receive( :parse_hosts_file ).and_raise( Errno::ENOENT ) mock_beaker_hostgenerator_cli = Object.new expect( BeakerHostGenerator::CLI ).to receive( :new ).and_return( mock_beaker_hostgenerator_cli ) expect( mock_beaker_hostgenerator_cli ).to receive( :execute ).and_raise( BeakerHostGenerator::Exceptions::InvalidNodeSpecError ) expect( Beaker::Options::HostsFileParser ).not_to receive( :parse_hosts_string ) expect( $stdout ).to receive( :puts ).with( /does not exist/ ).ordered expect( $stderr ).to receive( :puts ).with( /Exiting with an Error/ ).ordered expect { parser.parse_hosts_options }.to raise_error( BeakerHostGenerator::Exceptions::InvalidNodeSpecError ) end it 'can be passed a nil hosts file and get the default hash back' do parser.instance_variable_set( :@options, {} ) host_options = parser.parse_hosts_options expect(host_options[:HOSTS]).to be === {} end end end context "set_default_host!" do let(:roles) { @roles || [["master", "agent", "database"], ["agent"]] } let(:node1) { {:node1 => {:roles => roles[0]}} } let(:node2) { {:node2 => {:roles => roles[1]}} } let(:hosts) { node1.merge(node2) } it "does nothing if the default host is already set" do @roles = [["master"], ["agent", "default"]] parser.set_default_host!(hosts) expect(hosts[:node1][:roles].include?('default')).to be === false expect(hosts[:node2][:roles].include?('default')).to be === true end it "makes the master default" do @roles = [["master"], ["agent"]] parser.set_default_host!(hosts) expect(hosts[:node1][:roles].include?('default')).to be === true expect(hosts[:node2][:roles].include?('default')).to be === false end it "makes a single node default" do @roles = [["master", "database", "dashboard", "agent"]] parser.set_default_host!(node1) expect(hosts[:node1][:roles].include?('default')).to be === true end it "makes a single non-master node default" do @roles = [["database", "dashboard", "agent"]] parser.set_default_host!(node1) expect(hosts[:node1][:roles].include?('default')).to be === true end it "raises an error if two nodes are defined as default" do @roles = [["master", "default"], ["default"]] expect { parser.set_default_host!(hosts) }.to raise_error(ArgumentError) end end describe "normalize_args" do let(:hosts) do Beaker::Options::OptionsHash.new.merge({ 'HOSTS' => { :master => { :roles => ["master", "agent", "arbitrary_role"], :platform => 'el-7-x86_64', :user => 'root', }, :agent => { :roles => ["agent", "default", "other_abitrary_role"], :platform => 'el-7-x86_64', :user => 'root', }, }, 'fail_mode' => 'slow', 'preserve_hosts' => 'always', 'host_tags' => {} }) end def fake_hosts_file_for_platform(hosts, platform) hosts['HOSTS'].values.each { |h| h[:platform] = platform } filename = "hosts_file_#{platform}" File.open(filename, "w") do |file| YAML.dump(hosts, file) end filename end shared_examples_for(:a_platform_supporting_only_agents) do |platform, _type| it "restricts #{platform} hosts to agent" do args = [] args << '--hosts' << fake_hosts_file_for_platform(hosts, platform) expect { parser.parse_args(args) }.to raise_error(ArgumentError, /#{platform}.*may not have roles: master, database, dashboard/) end end context "restricts agents" do it_should_behave_like(:a_platform_supporting_only_agents, 'windows-version-arch') it_should_behave_like(:a_platform_supporting_only_agents, 'el-4-arch') end context "ssh user" do it 'uses the ssh[:user] if it is provided' do hosts['HOSTS'][:master][:ssh] = {:user => 'hello'} parser.instance_variable_set(:@options, hosts) parser.normalize_args expect(hosts['HOSTS'][:master][:user]).to be == 'hello' end it 'uses default user if there is an ssh hash, but no ssh[:user]' do hosts['HOSTS'][:master][:ssh] = {:hello => 'hello'} parser.instance_variable_set(:@options, hosts) parser.normalize_args expect(hosts['HOSTS'][:master][:user]).to be == 'root' end it 'uses default user if no ssh hash' do parser.instance_variable_set(:@options, hosts) parser.normalize_args expect(hosts['HOSTS'][:master][:user]).to be == 'root' end end end describe '#normalize_tags!' do let (:test_tag_and ) { @test_tag_and || [] } let (:test_tag_or ) { @test_tag_or || [] } let (:test_tag_exclude ) { @test_tag_exclude || [] } let (:options ) { opts = Beaker::Options::OptionsHash.new opts[:test_tag_and] = test_tag_and opts[:test_tag_or] = test_tag_or opts[:test_tag_exclude] = test_tag_exclude opts } it 'does not error if no tags overlap' do @test_tag_and = 'can,tommies,potatoes,plant' @test_tag_or = 'juicy,zoomba,plantation' @test_tag_exclude = 'joey,long_running,pants' parser.instance_variable_set(:@options, options) expect { parser.normalize_test_tags! }.not_to raise_error end it 'splits the basic case correctly' do @test_tag_and = 'can,tommies,potatoes,plant' @test_tag_or = 'johnny,wordsmith,zebra' @test_tag_exclude = 'joey,long_running,pants' parser.instance_variable_set(:@options, options) parser.normalize_test_tags! expect(options[:test_tag_and] ).to be === ['can', 'tommies', 'potatoes', 'plant'] expect(options[:test_tag_or] ).to be === ['johnny', 'wordsmith', 'zebra'] expect(options[:test_tag_exclude]).to be === ['joey', 'long_running', 'pants'] end it 'returns empty arrays for empty strings' do @test_tag_and = '' @test_tag_or = '' @test_tag_exclude = '' parser.instance_variable_set(:@options, options) parser.normalize_test_tags! expect(options[:test_tag_and] ).to be === [] expect(options[:test_tag_or] ).to be === [] expect(options[:test_tag_exclude]).to be === [] end it 'lowercases all tags correctly for later use' do @test_tag_and = 'jeRRy_And_tOM,PARka' @test_tag_or = 'clearLy_They,Neva' @test_tag_exclude = 'lEet_spEAK,pOland' parser.instance_variable_set(:@options, options) parser.normalize_test_tags! expect(options[:test_tag_and] ).to be === ['jerry_and_tom', 'parka'] expect(options[:test_tag_or] ).to be === ['clearly_they', 'neva'] expect(options[:test_tag_exclude]).to be === ['leet_speak', 'poland'] end end describe '#resolve_symlinks' do let (:options) { Beaker::Options::OptionsHash.new } it 'calls File.realpath if hosts_file is set' do options[:hosts_file] = opts_path parser.instance_variable_set(:@options, options) parser.resolve_symlinks! expect(parser.instance_variable_get(:@options)[:hosts_file]).to be === opts_path end it 'does not throw an error if hosts_file is not set' do options[:hosts_file] = nil parser.instance_variable_set(:@options, options) expect { parser.resolve_symlinks! }.to_not raise_error end end describe '#get_hypervisors' do it 'returns a unique list' do hosts_dupe = { 'vm1' => {hypervisor: 'hi'}, 'vm2' => {hypervisor: 'hi'}, 'vm3' => {hypervisor: 'bye'} } hosts_single = {'vm1' => {hypervisor: 'hi'}} expect(parser.get_hypervisors(hosts_dupe)).to eq(%w(hi bye)) expect(parser.get_hypervisors(hosts_single)).to eq(%w(hi)) end end describe '#get_roles' do it 'returns a unique list' do roles_dupe = { 'vm1' => {roles: ['master']}, 'vm2' => {roles: %w(database dashboard)}, 'vm3' => {roles: ['bye']} } roles_single = {'vm1' => {roles: ['hi']}} expect(parser.get_roles(roles_dupe)).to eq([['master'], %w(database dashboard), ['bye']]) expect(parser.get_roles(roles_single)).to eq([['hi']]) end end describe '#check_hypervisor_config' do let (:options) { Beaker::Options::OptionsHash.new } let (:invalid_file) { '/tmp/doesnotexist_visor.yml' } before :each do FakeFS.deactivate! end it 'checks ec2_yaml when blimpy' do options[:ec2_yaml] = hosts_path options[:dot_fog] = invalid_file parser.instance_variable_set(:@options, options) expect { parser.check_hypervisor_config('blimpy') }.to_not raise_error end it 'throws an error if ec2_yaml for blimpy is invalid' do options[:ec2_yaml] = invalid_file options[:dot_fog] = hosts_path parser.instance_variable_set(:@options, options) expect { parser.check_hypervisor_config('blimpy') }.to raise_error(ArgumentError, /required by blimpy/) end %w(aix solaris vcloud).each do |visor| it "checks dot_fog when #{visor}" do options[:ec2_yaml] = invalid_file options[:dot_fog] = hosts_path parser.instance_variable_set(:@options, options) expect { parser.check_hypervisor_config(visor) }.to_not raise_error end it "throws an error if dot_fog for #{visor} is invalid" do options[:ec2_yaml] = hosts_path options[:dot_fog] = invalid_file parser.instance_variable_set(:@options, options) expect { parser.check_hypervisor_config(visor) }.to raise_error(ArgumentError, /required by #{visor}/) end end it 'does not throw error on unknown visor' do expect { parser.check_hypervisor_config('unknown_visor') }.to_not raise_error end end end end end beaker-4.30.0/spec/beaker/options/presets_spec.rb000066400000000000000000000025371407603575700217560ustar00rootroot00000000000000require "spec_helper" module Beaker module Options describe Presets do let(:presets) { Presets.new } it "returns an env_vars OptionsHash" do expect(presets.env_vars).to be_instance_of(Beaker::Options::OptionsHash) end it "pulls in env vars of the form ':q_*' and adds them to the :answers of the OptionsHash" do ENV['q_puppet_cloud_install'] = 'n' env = presets.env_vars expect(env[:answers][:q_puppet_cloud_install]).to be === 'n' expect(env[:answers]['q_puppet_cloud_install']).to be === 'n' ENV.delete('q_puppet_cloud_install') end it "correctly parses the run_in_parallel array" do ENV['BEAKER_RUN_IN_PARALLEL'] = "install,configure" env = presets.env_vars expect(env[:run_in_parallel]).to eq(['install', 'configure']) end it "removes all empty/nil entries in env_vars" do expect(presets.env_vars.has_value?(nil)).to be === false expect(presets.env_vars.has_value?({})).to be === false end it "returns a presets OptionsHash" do expect(presets.presets).to be_instance_of(Beaker::Options::OptionsHash) end it 'has empty host_tags' do expect(presets.presets.has_key?(:host_tags)).to be_truthy expect(presets.presets[:host_tags]).to eq({}) end end end end beaker-4.30.0/spec/beaker/options/subcommand_options_parser_spec.rb000066400000000000000000000043701407603575700255450ustar00rootroot00000000000000require 'spec_helper' module Beaker module Options describe '#parse_subcommand_options' do let(:home_options_file_path) {ENV['HOME']+'/.beaker/subcommand_options.yaml'} let(:parser_mod) { Beaker::Options::SubcommandOptionsParser } let( :parser ) {parser_mod.parse_subcommand_options(argv, options_file)} let( :file_parser ){parser_mod.parse_options_file({})} let( :argv ) {[]} let( :options_file ) {""} it 'returns an empty OptionsHash if not executing a subcommand' do expect(parser).to be_kind_of(OptionsHash) expect(parser).to be_empty end describe 'when the subcommand is init' do let( :argv ) {['init']} it 'returns an empty OptionsHash' do expect(parser).to be_kind_of(OptionsHash) expect(parser).to be_empty end end describe 'when the subcommand is not init' do let( :argv ) {['provision']} let(:options_file) {Beaker::Subcommands::SubcommandUtil::SUBCOMMAND_OPTIONS} it 'calls parse_options_file with subcommand options file when home_dir is false' do allow(parser_mod).to receive(:execute_subcommand?).with('provision').and_return true allow(parser_mod).to receive(:parse_options_file).with(Beaker::Subcommands::SubcommandUtil::SUBCOMMAND_OPTIONS) end let( :options_file ) {home_options_file_path} it 'calls parse_options_file with home directory options file when home_dir is true' do allow(parser_mod).to receive(:execute_subcommand?).with('provision').and_return true allow(parser_mod).to receive(:parse_options_file).with(home_options_file_path) end it 'checks for file existence and loads the YAML file' do allow(File).to receive(:exist?).and_return true allow(YAML).to receive(:load_file).and_return({}) expect(file_parser).to be_kind_of(Hash) expect(file_parser).not_to be_kind_of(OptionsHash) end let(:options_file) {""} it 'returns an empty options hash when file does not exist' do allow(File).to receive(:exist?).and_return false expect(parser).to be_kind_of(OptionsHash) expect(parser).to be_empty end end end end end beaker-4.30.0/spec/beaker/options/validator_spec.rb000066400000000000000000000167531407603575700222630ustar00rootroot00000000000000require 'spec_helper' module Beaker module Options describe Validator do let(:validator) { Validator.new } describe '#check_yaml_file' do let(:bad_yaml_path) { File.join(File.expand_path(File.dirname(__FILE__)), 'data', 'badyaml.cfg') } let(:yaml_path) { File.join(File.expand_path(File.dirname(__FILE__)), 'data', 'hosts.cfg') } before :each do FakeFS.deactivate! end it 'raises error on improperly formatted yaml file' do expect { validator.check_yaml_file(bad_yaml_path) }.to raise_error(ArgumentError) end it 'raises an error when a yaml file is missing' do expect { validator.check_yaml_file('not a path') }.to raise_error(ArgumentError) end it 'does not throw errors on valid yaml files' do expect { validator.check_yaml_file(yaml_path) }.to_not raise_error end end describe '#validator_error' do it 'raises error with message' do expect { validator.validator_error('test error') }.to raise_error(ArgumentError, 'test error') end end describe '#default_set?' do it 'is false when empty' do expect(validator.default_set?([])).to be_falsey end it 'throws error when more than 1' do expect { validator.default_set?([1, 2]) }.to raise_error(ArgumentError) end ['test', 1, 3.4, true, Object.new].each do |val| it "is true when contents are #{val.class}" do expect(validator.default_set?([val])).to be_truthy end end end describe '#valid_fail_mode?' do %w(stop fast slow).each do |val| it "does not throw error when set to #{val}" do expect { validator.validate_fail_mode(val) }.to_not raise_error end it "raises error when set to #{val.upcase}" do expect { validator.validate_fail_mode(val.upcase) }.to raise_error(ArgumentError) end it "raises error when set to #{val.capitalize}" do expect { validator.validate_fail_mode(val.capitalize) }.to raise_error(ArgumentError) end end ['test', 1, true, Object.new].each do |val| it 'raises error with invalid mode' do expect { validator.validate_fail_mode(val) }.to raise_error(ArgumentError) end end end describe '#valid_preserve_hosts?' do %w(always onfail onpass never).each do |val| it "does not raise error when set to #{val}" do expect { validator.validate_preserve_hosts(val) }.to_not raise_error end it "raises error when set to #{val.upcase}" do expect { validator.validate_preserve_hosts(val.upcase) }.to raise_error(ArgumentError) end it "raises error when set to #{val.capitalize}" do expect { validator.validate_preserve_hosts(val.capitalize) }.to raise_error(ArgumentError) end end ['test', 1, true, Object.new].each do |val| it 'raises error with invalid setting' do expect { validator.validate_preserve_hosts(val) }.to raise_error(ArgumentError) end end end describe '#validate_test_tags' do it 'does error if tags overlap' do tag_includes = %w(can tommies should_error potatoes plant) tag_excludes = %w(joey long_running pants should_error) expect { validator.validate_test_tags(tag_includes, [], tag_excludes) }.to raise_error(ArgumentError) end it 'does not raise an error if tags do not overlap' do tag_includes = %w(horse dog cat) tag_excludes = %w(car truck train) expect { validator.validate_test_tags(tag_includes, [], tag_excludes) }.to_not raise_error end it 'raises an error if AND and OR are both used' do # this is because we don't have a way to specify how they # should interact tag_and = %w(square) tag_or = %w(circle) expect { validator.validate_test_tags(tag_and, tag_or, []) }.to raise_error(ArgumentError) end end describe '#validate_frictionless_roles' do it 'does nothing when roles are correct' do expect { validator.validate_frictionless_roles(%w(frictionless)) }.to_not raise_error expect { validator.validate_frictionless_roles(%w(frictionless agent)) }.to_not raise_error expect { validator.validate_frictionless_roles(%w(frictionless test1)) }.to_not raise_error expect { validator.validate_frictionless_roles(%w(frictionless a role)) }.to_not raise_error expect { validator.validate_frictionless_roles(%w(frictionless frictionless some_role)) }.to_not raise_error end it 'throws errors when roles conflict' do expect { validator.validate_frictionless_roles(%w(frictionless master)) }.to raise_error(ArgumentError) expect { validator.validate_frictionless_roles(%w(frictionless database)) }.to raise_error(ArgumentError) expect { validator.validate_frictionless_roles(%w(frictionless dashboard)) }.to raise_error(ArgumentError) expect { validator.validate_frictionless_roles(%w(frictionless console)) }.to raise_error(ArgumentError) expect { validator.validate_frictionless_roles(%w(frictionless master database dashboard console)) }.to raise_error(ArgumentError) end end describe '#validate_master_count' do it 'does nothing when count is exactly 1' do expect { validator.validate_master_count(1) }.to_not raise_error end it 'throws errors when greater than 1' do expect { validator.validate_master_count(2) }.to raise_error(ArgumentError, /one host\/node/) expect { validator.validate_master_count(5) }.to raise_error(ArgumentError, /one host\/node/) expect { validator.validate_master_count(100) }.to raise_error(ArgumentError, /one host\/node/) end end describe '#validate_files' do it 'does not throw an error with non-empty list' do expect { validator.validate_files(['filea'], '.') }.to_not raise_error expect { validator.validate_files(%w(filea fileb), '.') }.to_not raise_error end it 'raises error when file list is empty' do expect { validator.validate_files([], '.') }.to raise_error(ArgumentError) end end describe '#validate_path' do it 'does not throw an error when path is valid' do FakeFS do expect { validator.validate_path('.') }.to_not raise_error end end it 'throws an error whe path is invalid' do expect { validator.validate_path('/tmp/doesnotexist_test') }.to raise_error(ArgumentError) end end describe '#validate_platform' do let(:valid_platform) { {'platform' => 'test1'} } let(:blank_platform) { {'platform' => ''} } it 'does not throw an error when host has a platform' do expect { validator.validate_platform(valid_platform, 'vm1') }.to_not raise_error end it 'throws an error when platform is not included' do expect { validator.validate_platform({}, 'vm1') }.to raise_error(ArgumentError, /Host vm1 does not/) expect { validator.validate_platform(blank_platform, 'vm2') }.to raise_error(ArgumentError, /Host vm2 does not/) end end end end end beaker-4.30.0/spec/beaker/perf_spec.rb000066400000000000000000000076411407603575700175330ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Perf do context "When a Perf object is created" do it 'creates a new Perf object' do hosts = Array.new options = Hash.new options[:log_level] = :debug my_logger = Beaker::Logger.new(options) options[:logger] = my_logger perf = Perf.new( hosts, options ) expect( perf ).to be_a_kind_of Perf end before(:each) do @options = make_opts @options[:collect_perf_data] = true @options[:log_level] = :debug @options[:color] = false @my_io = StringIO.new @my_logger = Beaker::Logger.new(@options) @my_logger.add_destination(@my_io) @options[:logger] = @my_logger end it 'creates a new Perf object with a single host, :collect_perf_data = true' do hosts = [ make_host("myHost", @options) ] hosts.each { |host| host['platform'] = "centos-6-x86_64" } @my_logger.remove_destination(STDOUT) perf = Perf.new( hosts, @options ) expect( perf ).to be_a_kind_of Perf expect(@my_io.string).to match(/Setup perf on host: myHost/) end it 'creates a new Perf object with multiple hosts, :collect_perf_data = true' do hosts = [ make_host("myHost", @options), make_host("myOtherHost", @options) ] hosts.each { |host| host['platform'] = "centos-6-x86_64" } @my_logger.remove_destination(STDOUT) perf = Perf.new( hosts, @options ) expect( perf ).to be_a_kind_of Perf expect(@my_io.string).to match(/Setup perf on host: myHost*\nSetup perf on host: myOtherHost/) end it 'creates a new Perf object with multiple hosts, :collect_perf_data = true, SLES' do hosts = [ make_host("myHost", @options), make_host("myOtherHost", @options), make_host("myThirdHost", @options) ] hosts[0]['platform'] = "centos-6-x86_64" hosts[1]['platform'] = "sles-11-x86_64" hosts[2]['platform'] = "opensuse-15-x86_64" @my_logger.remove_destination(STDOUT) perf = Perf.new( hosts, @options ) expect( perf ).to be_a_kind_of Perf expect(@my_io.string).to match(/Setup perf on host: myHost\nSetup perf on host: myOtherHost/) end end context "When testing is finished, :collect_perf_data = true" do before(:each) do @options = make_opts @options[:collect_perf_data] = true @options[:log_level] = :debug @options[:color] = false @hosts = [ make_host("myHost", @options), make_host("myOtherHost", @options) ] @my_io = StringIO.new @my_logger = Beaker::Logger.new(@options) @my_logger.add_destination(@my_io) @options[:logger] = @my_logger end it "Does the Right Thing on Linux hosts" do @hosts[0]['platform'] = "centos-6-x86_64" @my_logger.remove_destination(STDOUT) perf = Perf.new( @hosts, @options ) expect( perf ).to be_a_kind_of Perf perf.print_perf_info expect(@my_io.string).to match(/Setup perf on host: myHost\nSetup perf on host: myOtherHost\nPerf \(sysstat\) not supported on host: myOtherHost\nGetting perf data for host: myHost\nGetting perf data for host: myOtherHost\nPerf \(sysstat\) not supported on host: myOtherHost/) end it "Does the Right Thing on non-Linux hosts" do @hosts[0]['platform'] = "windows" @my_logger.remove_destination(STDOUT) perf = Perf.new( @hosts, @options ) expect( perf ).to be_a_kind_of Perf perf.print_perf_info expect(@my_io.string).to match(/Setup perf on host: myHost\nPerf \(sysstat\) not supported on host: myHost\nSetup perf on host: myOtherHost\nPerf \(sysstat\) not supported on host: myOtherHost\nGetting perf data for host: myHost\nPerf \(sysstat\) not supported on host: myHost\nGetting perf data for host: myOtherHost\nPerf \(sysstat\) not supported on host: myOtherHost/) end end end end beaker-4.30.0/spec/beaker/platform_spec.rb000066400000000000000000000120101407603575700204050ustar00rootroot00000000000000require 'spec_helper' module Beaker describe Platform do let( :logger ) { double( 'logger' ) } let( :platform ) { Platform.new(@name) } context 'initialize' do describe "recognizes valid platforms" do it "accepts correctly formatted platform values" do @name = 'oracle-version-arch' expect{ platform }.not_to raise_error end it "rejects non-supported osfamilies" do @name = 'amazon6-version-arch' expect{ platform }.to raise_error(ArgumentError) end it "rejects platforms without version/arch" do @name = 'ubuntu-5' expect{ platform }.to raise_error(ArgumentError) end it "rejects platforms that do not have osfamily at start of string" do @name = 'o3l-r5-u6-x86' expect{ platform }.to raise_error(ArgumentError) end end describe "if platform does not have codename" do it "sets codename to nil" do @name = "centos-6.5-x86_64" expect(platform.codename).to be_nil end end describe "platforms with version and codename" do it "intializes both version and codename if given version" do @name = "debian-7-x86_64" expect(platform.version).to eq('7') expect(platform.codename).to eq('wheezy') end it "intializes both version and codename if given codename" do @name = "debian-wheezy-x86_64" expect(platform.version).to eq('7') expect(platform.codename).to eq('wheezy') end end end context 'to_array' do it "converts Beaker::Platform object to array of its attribues" do @name = 'debian-7-somethingsomething' expect( platform.to_array ).to be === ['debian', '7', 'somethingsomething', 'wheezy'] end end context 'with_version_codename' do it "can convert debian-11-xxx to debian-bullseye-xxx" do @name = 'debian-11-xxx' expect( platform.with_version_codename ).to be === 'debian-bullseye-xxx' end it "can convert debian-7-xxx to debian-wheezy-xxx" do @name = 'debian-7-xxx' expect( platform.with_version_codename ).to be === 'debian-wheezy-xxx' end it "can convert debian-6-xxx to debian-squeeze-xxx" do @name = 'debian-6-xxx' expect( platform.with_version_codename ).to be === 'debian-squeeze-xxx' end it "can convert unbuntu-2004-xxx to ubuntu-focal-xxx" do @name = 'ubuntu-2004-xxx' expect( platform.with_version_codename ).to be === 'ubuntu-focal-xxx' end it "can convert unbuntu-1604-xxx to ubuntu-xenial-xxx" do @name = 'ubuntu-1604-xxx' expect( platform.with_version_codename ).to be === 'ubuntu-xenial-xxx' end it "can convert ubuntu-1310-xxx to ubuntu-saucy-xxx" do @name = 'ubuntu-1310-xxx' expect( platform.with_version_codename ).to be === 'ubuntu-saucy-xxx' end it "can convert ubuntu-12.10-xxx to ubuntu-quantal-xxx" do @name = 'ubuntu-12.10-xxx' expect( platform.with_version_codename ).to be === 'ubuntu-quantal-xxx' end it "can convert ubuntu-10.04-xxx to ubuntu-lucid-xxx" do @name = 'ubuntu-10.04-xxx' expect( platform.with_version_codename ).to be === 'ubuntu-lucid-xxx' end ['centos','redhat'].each do |p| it "leaves #{p}-7-xxx alone" do @name = "#{p}-7-xxx" expect( platform.with_version_codename ).to be === "#{p}-7-xxx" end end end context 'with_version_number' do it "can convert debian-wheezy-xxx to debian-7-xxx" do @name = 'debian-wheezy-xxx' expect( platform.with_version_number ).to be === 'debian-7-xxx' end it "can convert debian-squeeze-xxx to debian-6-xxx" do @name = 'debian-squeeze-xxx' expect( platform.with_version_number ).to be === 'debian-6-xxx' end it "can convert ubuntu-saucy-xxx to ubuntu-1310-xxx" do @name = 'ubuntu-saucy-xxx' expect( platform.with_version_number ).to be === 'ubuntu-1310-xxx' end it "can convert ubuntu-quantal-xxx to ubuntu-1210-xxx" do @name = 'ubuntu-quantal-xxx' expect( platform.with_version_number ).to be === 'ubuntu-1210-xxx' end ['centos','redhat'].each do |p| it "leaves #{p}-7-xxx alone" do @name = "#{p}-7-xxx" expect( platform.with_version_number ).to be === "#{p}-7-xxx" end end end context 'round tripping from yaml', if: RUBY_VERSION =~ /^1\.9/ do before do @name = 'ubuntu-14.04-x86_64' end let(:round_tripped) { YAML.load(YAML.dump(platform)) } [:variant, :arch, :version, :codename].each do |field| it "deserializes the '#{field}' field" do expect(round_tripped.send(field)).to eq platform.send(field) end end it 'properly sets the string contents' do expect(round_tripped.to_s).to eq @name end end end end beaker-4.30.0/spec/beaker/shared/000077500000000000000000000000001407603575700164765ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/shared/error_handler_spec.rb000066400000000000000000000016431407603575700226670ustar00rootroot00000000000000require 'spec_helper' module Beaker module Shared describe ErrorHandler do let( :backtrace ) { "I'm the backtrace\nYes I am!\nI have important information" } let( :logger ) { double( 'logger' ) } before :each do allow( logger ).to receive( :error ).and_return( true ) allow( logger ).to receive( :pretty_backtrace ).and_return( backtrace ) end context 'report_and_raise' do it "records the backtrace of the exception to the logger" do ex = Exception.new("ArgumentError") allow( ex ).to receive( :backtrace ).and_return(backtrace) mesg = "I'm the extra message" backtrace.each_line do |line| expect( logger ).to receive( :error ).with(line) end expect( subject ).to receive( :raise ).once subject.report_and_raise(logger, ex, mesg) end end end end end beaker-4.30.0/spec/beaker/shared/fog_credentials_spec.rb000066400000000000000000000103121407603575700231620ustar00rootroot00000000000000require 'spec_helper' module Beaker module Shared describe FogCredentials do context "#get_fog_credentials" do it 'raises ArgumentError when fog file is missing' do expect{ get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog' ) }.to raise_error( ArgumentError ) end it 'raises ArgumentError when fog file is empty' do expect( File ).to receive( :open ) { "" } expect{ get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog') }.to raise_error( ArgumentError ) end it 'raises ArgumentError when fog file does not contain "default" section and no section is specified' do data = { :some => { :other => :data } } expect( YAML ).to receive( :load_file ) { data } expect{ get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog' ) }.to raise_error( ArgumentError ) end it 'raises ArgumentError when fog file does not contain another section passed by argument' do data = { :some => { :other => :data } } expect( YAML ).to receive( :load_file ) { data } expect{ get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog', credential = :other_credential ) }.to raise_error( ArgumentError ) end it 'raises ArgumentError when there are formatting errors in the fog file' do data = { "'default'" => { :vmpooler_token => "b2wl8prqe6ddoii70md" } } expect( YAML ).to receive( :load_file ) { data } expect{ get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog' ) }.to raise_error( ArgumentError ) end it 'raises ArgumentError when there are syntax errors in the fog file' do data = ";default;\n :vmpooler_token: z2wl8prqe0ddoii707d" allow( File ).to receive( :open ).and_yield( StringIO.new( data ) ) expect{ get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog' ) }.to raise_error( ArgumentError, /Psych::SyntaxError/ ) end it 'returns the named credential section' do data = { :default => { :vmpooler_token => "wrong_token"}, :other_credential => { :vmpooler_token => "correct_token" } } expect( YAML ).to receive( :load_file ) { data } expect( get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog', credential = :other_credential )[:vmpooler_token] ).to eq( "correct_token" ) end it 'returns the named credential section from ENV["FOG_CREDENTIAL"]' do ENV['FOG_CREDENTIAL'] = 'other_credential' data = { :default => { :vmpooler_token => "wrong_token"}, :other_credential => { :vmpooler_token => "correct_token" } } expect( YAML ).to receive( :load_file ) { data } expect( get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog' )[:vmpooler_token] ).to eq( "correct_token" ) ENV.delete( 'FOG_CREDENTIAL' ) end it 'returns the named credential section from ENV["FOG_CREDENTIAL"] even when an argument is provided' do ENV['FOG_CREDENTIAL'] = 'other_credential' data = { :default => { :vmpooler_token => "wrong_token"}, :other_credential => { :vmpooler_token => "correct_token" } } expect( YAML ).to receive( :load_file ) { data } expect( get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog', credential = :default )[:vmpooler_token] ).to eq( "correct_token" ) ENV.delete( 'FOG_CREDENTIAL' ) end it 'returns the named credential section from ENV["FOG_RC"] path' do ENV['FOG_RC'] = '/some/other/path/to/.fog' data = { :default => { :vmpooler_token => "correct_token"}, :other_credential => { :vmpooler_token => "wrong_token" } } expect( YAML ).to receive( :load_file ).with( '/some/other/path/to/.fog' ) { data } expect( get_fog_credentials( fog_file_path = '/path/that/does/not/exist/.fog', credential = :default )[:vmpooler_token] ).to eq( "correct_token" ) end end end end end beaker-4.30.0/spec/beaker/shared/host_manager_spec.rb000066400000000000000000000146621407603575700225150ustar00rootroot00000000000000require 'spec_helper' module Beaker module Shared config = RSpec::Mocks.configuration config.patch_marshal_to_support_partial_doubles = true describe HostManager do # The logger double as nil object doesn't work with marshal.load and marshal.unload needed for run_in_parallel. let( :logger ) { double('logger') } let( :host_handler ) { Beaker::Shared::HostManager } let( :spec_block ) { Proc.new { |arr| arr } } let( :platform ) { @platform || 'unix' } let( :role0 ) { "role0" } let( :role1 ) { :role1 } let( :role2 ) { :role2 } let( :hosts ) { hosts = make_hosts( { :platform => platform } ) hosts[0][:roles] = ['agent', role0] hosts[1][:roles] = ['master', 'dashboard', 'agent', 'database', role1] hosts[2][:roles] = ['agent', role2] hosts } context "#hosts_with_name" do it "can identify the host by name" do expect( host_handler.hosts_with_name( hosts, 'vm1' )).to be === [hosts[0]] end it "can identify the host by vmhostname" do hosts[0][:vmhostname] = 'myname.whatever' expect( host_handler.hosts_with_name( hosts, 'myname.whatever' )).to be === [hosts[0]] end it "can identify the host by ip" do hosts[0][:ip] = '0.0.0.0' expect( host_handler.hosts_with_name( hosts, '0.0.0.0' )).to be === [hosts[0]] end it "returns [] when no match is found in a set of hosts" do hosts[0][:ip] = '0.0.0.0' hosts[0][:vmhostname] = 'myname.whatever' expect( host_handler.hosts_with_name( hosts, 'surprise' )).to be === [] end end context "#hosts_with_role" do it "can find the master in a set of hosts" do expect( host_handler.hosts_with_role( hosts, 'master' ) ).to be === [hosts[1]] end it "can find all agents in a set of hosts" do expect( host_handler.hosts_with_role( hosts, 'agent' ) ).to be === hosts end it "returns [] when no match is found in a set of hosts" do expect( host_handler.hosts_with_role( hosts, 'surprise' ) ).to be === [] end end context "#only_host_with_role" do it "can find the single master in a set of hosts" do expect( host_handler.only_host_with_role( hosts, 'master' ) ).to be === hosts[1] end it "throws an error when more than one host with matching role is found" do expect{ host_handler.only_host_with_role( hosts, 'agent' ) }.to raise_error(ArgumentError) end it "throws an error when no host is found matching the role" do expect{ host_handler.only_host_with_role( hosts, 'surprise' ) }.to raise_error(ArgumentError) end it "throws an error when role = nil" do expect{ host_handler.find_at_most_one_host_with_role( hosts, nil ) }.to raise_error(ArgumentError) end end context "#find_at_most_one_host_with_role" do it "can find the single master in a set of hosts" do expect( host_handler.find_at_most_one_host_with_role( hosts, 'master' ) ).to be === hosts[1] end it "throws an error when more than one host with matching role is found" do expect{ host_handler.find_at_most_one_host_with_role( hosts, 'agent' ) }.to raise_error(ArgumentError) end it "returns nil when no host is found matching the role" do expect( host_handler.find_at_most_one_host_with_role( hosts, 'surprise' ) ).to be_nil end it "throws an error when role = nil" do expect{ host_handler.find_at_most_one_host_with_role( hosts, nil ) }.to raise_error(ArgumentError) end end context "#run_block_on" do it "can execute a block against hosts identified by a string" do myhosts = host_handler.run_block_on( hosts, role0 ) do |hosts| hosts end expect( myhosts ).to be === hosts[0] end it "can execute a block against hosts identified by a hostname" do myhosts = host_handler.run_block_on( hosts, hosts[0].name ) do |hosts| hosts end expect( myhosts ).to be === hosts[0] end it "can execute a block against an array of hosts" do myhosts = host_handler.run_block_on( hosts ) do |hosts| hosts end expect( myhosts ).to be === hosts end it "can execute a block against an array of hosts in parallel" do InParallel::InParallelExecutor.logger = Logger.new(STDOUT) FakeFS.deactivate! expect( InParallel::InParallelExecutor ).to receive(:_execute_in_parallel).with(any_args).and_call_original.exactly(3).times myhosts = host_handler.run_block_on( hosts, nil, { :run_in_parallel => true } ) do |host| # kind of hacky workaround to remove logger which contains a singleton method injected by rspec host.instance_eval("remove_instance_variable(:@logger)") host end # After marshal load and marshal unload, the logger option (an rspec double) is no longer 'equal' to the original. # Array of results can be in different order. new_host = myhosts.select{ |host| host.name == hosts[0].name}.first hosts[0].options.each { |option| expect(option[1]).to eq(new_host.options[option[0]]) unless option[0] == :logger } end it "will ignore run_in_parallel global option" do myhosts = host_handler.run_block_on( hosts, nil, { :run_in_parallel => [] } ) do |host| host end expect( InParallel::InParallelExecutor ).not_to receive(:_execute_in_parallel).with(any_args) expect(myhosts).to eq(hosts) end it "does not run in parallel if there is only 1 host in the array" do myhosts = host_handler.run_block_on( [hosts[0]], nil, { :run_in_parallel => true } ) do |host| puts host host end expect( myhosts ).to be === [hosts[0]] end it "receives an ArgumentError on empty host" do expect { host_handler.run_block_on( [], role0 ) }.to raise_error(ArgumentError) end end end end end beaker-4.30.0/spec/beaker/shared/options_resolver_spec.rb000066400000000000000000000040251407603575700234520ustar00rootroot00000000000000require 'spec_helper' module Beaker module Shared describe OptionsResolver do describe 'run_in_parallel?' do it 'returns true if :run_in_parallel in opts is true' do expect( subject.run_in_parallel?({:run_in_parallel => true}, nil, nil) ).to be === true end it 'returns false if :run_in_parallel in opts is false' do expect( subject.run_in_parallel?({:run_in_parallel => false}, nil, nil) ).to be === false end it 'returns false if :run_in_parallel in opts is an empty array' do expect( subject.run_in_parallel?({:run_in_parallel => []}, nil, nil) ).to be === false end it 'returns false if :run_in_parallel in opts is an empty array but a mode is specified in options' do expect( subject.run_in_parallel?({:run_in_parallel => []}, {:run_in_parallel => ['install']}, 'install') ).to be === false end it 'returns true if opts is nil but a matching mode is specified in options' do expect( subject.run_in_parallel?(nil, {:run_in_parallel => ['install']}, 'install') ).to be === true end it 'returns false if opts is nil and a non matching mode is specified in options' do expect( subject.run_in_parallel?(nil, {:run_in_parallel => ['configure']}, 'install') ).to be === false end it 'returns true if opts is nil and a matching mode and a non matching mode is specified in options' do expect( subject.run_in_parallel?(nil, {:run_in_parallel => ['configure', 'install']}, 'install') ).to be === true end it 'returns false if opts is nil and no mode is specified in options' do expect( subject.run_in_parallel?(nil, {:run_in_parallel => []}, 'install') ).to be === false end it 'returns false if opts is false but a matching mode is specified in options' do expect( subject.run_in_parallel?({:run_in_parallel => false}, {:run_in_parallel => ['install']}, 'install') ).to be === false end end end end end beaker-4.30.0/spec/beaker/shared/repetition_spec.rb000066400000000000000000000056231407603575700222250ustar00rootroot00000000000000require 'spec_helper' module Beaker module Shared describe Repetition do describe '#repeat_for' do it "repeats a block for 5 seconds" do allow( Time ).to receive( :now ).and_return( 0, 1, 2, 3, 4, 5, 6 ) block = double( 'block' ) expect( block ).to receive( :exec ).exactly( 5 ).times.and_return( false ) subject.repeat_for( 5 ) do block.exec end end it "should short circuit if the block is complete" do allow( Time ).to receive( :now ).and_return( 0, 1, 2, 3, 4, 5 ) block = double( 'block' ) expect( block ).to receive( :exec ).once.and_return( true ) subject.repeat_for( 5 ) do block.exec end end end describe '#repeat_fibonacci_style_for' do let(:block) { double("block") } it "sleeps in fibonacci increasing intervals" do expect( block ).to receive( :exec ).exactly( 5 ).times.and_return( false ) allow( subject ).to receive( 'sleep' ).and_return( true ) expect( subject ).to receive( :sleep ).with( 1 ).exactly( 2 ).times expect( subject ).to receive( :sleep ).with( 2 ).once expect( subject ).to receive( :sleep ).with( 3 ).once expect( subject ).to receive( :sleep ).with( 5 ).once expect( subject ).to receive( :sleep ).with( 8 ).never subject.repeat_fibonacci_style_for( 5 ) do block.exec end end it "should short circuit if the block succeeds (returns true)" do expect(block).to receive(:exec).and_return(false).ordered.exactly(4).times expect(block).to receive(:exec).and_return( true).ordered.once expect(subject).to receive(:sleep).with(1).exactly(2).times expect(subject).to receive(:sleep).with(2).once expect(subject).to receive(:sleep).with(3).once expect(subject).to receive(:sleep).with(anything).never subject.repeat_fibonacci_style_for(20) do block.exec end end it "returns false if block never returns that it is done (true)" do expect(block).to receive(:abcd).exactly(3).times.and_return(false) expect(subject).to receive(:sleep).with(1).exactly(2).times expect(subject).to receive(:sleep).with(2).once expect(subject).to receive(:sleep).with(anything).never success_result = subject.repeat_fibonacci_style_for(3) do block.abcd end expect(success_result).to be false end it "never sleeps if block is successful right at first (returns true)" do expect(block).to receive(:fake01).once.and_return(true) expect(subject).to receive(:sleep).never subject.repeat_fibonacci_style_for(3) do block.fake01 end end end end end end beaker-4.30.0/spec/beaker/shared/semvar_spec.rb000066400000000000000000000070551407603575700213410ustar00rootroot00000000000000require 'spec_helper' module Beaker module Shared describe Semvar do describe 'version_is_less' do it 'reports 2015.3.0-rc0-8-gf80879a is less than 2016' do expect( subject.version_is_less( '2015.3.0-rc0-8-gf80879a', '2016' ) ).to be === true end it 'reports 2015.3.0-rc0-8-gf80879a is less than 2015.3.0' do expect( subject.version_is_less( '2015.3.0-rc0-8-gf80879a', '2015.3.0' ) ).to be === true end it 'reports that 2015.3.0-rc0 is less than 2015.3.0-rc0-8-gf80879a' do expect( subject.version_is_less( '2015.3.0-rc0', '2015.3.0-rc0-8-gf80879a' ) ).to be === true end it 'reports that 2015.3.0-rc2 is less than 2015.3.0-rc10 (not using string comparison)' do expect( subject.version_is_less( '2015.3.0-rc2', '2015.3.0-rc10' ) ).to be === true end it 'reports that 2015.3.0 is less than 2015.3.0-1-gabc1234' do expect( subject.version_is_less( '2015.3.0', '2015.3.0-1-gabc1234' ) ).to be === true end it 'reports that 2015.3.0-rc2 is less than 2015.3.0-1-gabc1234' do expect( subject.version_is_less( '2015.3.0-rc2', '2015.3.0-1-gabc1234' ) ).to be === true end it 'reports 2015.3.0-rc0-8-gf80879a is not less than 3.0.0' do expect( subject.version_is_less( '2015.3.0-rc0-8-gf80879a', '3.0.0' ) ).to be === false end it 'reports 3.0.0-160-gac44cfb is not less than 3.0.0' do expect( subject.version_is_less( '3.0.0-160-gac44cfb', '3.0.0' ) ).to be === false end it 'reports 3.0.0-160-gac44cfb is not less than 2.8.2' do expect( subject.version_is_less( '3.0.0-160-gac44cfb', '2.8.2' ) ).to be === false end it 'reports 3.0.0 is less than 3.0.0-160-gac44cfb' do expect( subject.version_is_less( '3.0.0', '3.0.0-160-gac44cfb' ) ).to be === true end it 'reports 2.8.2 is less than 3.0.0-160-gac44cfb' do expect( subject.version_is_less( '2.8.2', '3.0.0-160-gac44cfb' ) ).to be === true end it 'reports 2.8 is less than 3.0.0-160-gac44cfb' do expect( subject.version_is_less( '2.8', '3.0.0-160-gac44cfb' ) ).to be === true end it 'reports 2.8 is less than 2.9' do expect( subject.version_is_less( '2.8', '2.9' ) ).to be === true end it 'reports that 2015.3.0 is not less than 2015.3.0' do expect( subject.version_is_less( '2015.3.0', '2015.3.0' ) ).to be == false end end describe 'max_version' do it 'returns nil if versions isn\'t defined' do expect( subject.max_version(nil) ).to be_nil end it 'returns nil if versions is empty' do expect( subject.max_version([]) ).to be_nil end it 'allows you to set the default, & will return it with faulty input' do expect( subject.max_version([], '5.9') ).to be === '5.9' end it 'returns the one value if given a length 1 array' do expect( subject.max_version(['7.3']) ).to be === '7.3' end it 'does not mangle the versions array passed in' do first_array = ['1.4.3', '8.4.5', '3.5.7', '2.7.5'] array_to_pass = first_array.dup subject.max_version(array_to_pass) expect( array_to_pass ).to be === first_array end it 'returns 5.8.9 from [5.8.9, 1.2.3, 0.3.5, 5.7.11]' do expect( subject.max_version(['5.8.9', '1.2.3', '0.3.5', '5.7.11']) ).to be === '5.8.9' end end end end end beaker-4.30.0/spec/beaker/ssh_connection_spec.rb000066400000000000000000000233111407603575700216030ustar00rootroot00000000000000require 'spec_helper' require 'net/ssh' module Beaker describe SshConnection do let( :user ) { 'root' } let( :ssh_opts ) { { keepalive: true, keepalive_interval: 2 } } let( :options ) { { :logger => double('logger').as_null_object, :ssh_connection_preference => [:ip, :vmhostname, :hostname]} } let( :ip ) { "default.ip.address" } let( :vmhostname ){ "vmhostname" } let( :hostname) { "my_host" } let( :name_hash ) { { :ip => ip, :vmhostname => vmhostname, :hostname => hostname } } subject(:connection) { SshConnection.new name_hash, user, ssh_opts, options } before :each do allow( subject ).to receive(:sleep) end it 'self.connect creates connects and returns a proxy for that connection' do expect( Net::SSH ).to receive(:start).with( "default.ip.address", user, ssh_opts ).and_return(true) connection_constructor = SshConnection.connect name_hash, user, ssh_opts, options expect( connection_constructor ).to be_a_kind_of SshConnection end it 'connect creates a new connection' do expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts).and_return(true) connection.connect end it 'connect caches its connection' do expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts ).once.and_return true connection.connect end it 'attempts to connect by vmhostname address if ip connection fails' do expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts).and_return(false) expect( Net::SSH ).to receive( :start ).with( vmhostname, user, ssh_opts).and_return(true).once expect( Net::SSH ).to receive( :start ).with( hostname, user, ssh_opts).never connection.connect end it 'attempts to connect by hostname, if vmhost + ipaddress have failed' do expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts).and_return(false) expect( Net::SSH ).to receive( :start ).with( vmhostname, user, ssh_opts).and_return(false) expect( Net::SSH ).to receive( :start ).with( hostname, user, ssh_opts).and_return(true).once connection.connect end describe '#close' do it 'runs ssh close' do mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { mock_ssh } connection.connect allow( mock_ssh).to receive( :closed? ).once.and_return(false) expect( mock_ssh ).to receive( :close ).once connection.close end it 'sets the @ssh variable to nil' do mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { mock_ssh } connection.connect allow( mock_ssh).to receive( :closed? ).once.and_return(false) expect( mock_ssh ).to receive( :close ).once connection.close expect( connection.instance_variable_get(:@ssh) ).to be_nil end it 'calls ssh shutdown & re-raises if ssh close fails with an unexpected Error' do mock_ssh = Object.new allow( mock_ssh ).to receive( :close ) { raise StandardError } expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { mock_ssh } connection.connect allow( mock_ssh).to receive( :closed? ).once.and_return(false) expect( mock_ssh ).to receive( :shutdown! ).once expect{ connection.close }.to raise_error(StandardError) expect( connection.instance_variable_get(:@ssh) ).to be_nil end end describe '#execute' do it 'raises an error if it fails' do mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { mock_ssh } connection.connect allow( subject ).to receive( :try_to_execute ) { raise Timeout::Error } expect{ connection.execute('ls') }.to raise_error Timeout::Error end end describe '#request_terminal_for' do it 'fails correctly by raising Net::SSH::Exception' do mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { mock_ssh } connection.connect mock_channel = Object.new allow( mock_channel ).to receive( :request_pty ).and_yield(nil, false) expect{ connection.request_terminal_for mock_channel, 'ls' }.to raise_error Net::SSH::Exception end end describe '#register_stdout_for' do before :each do @mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { @mock_ssh } connection.connect @data = '7 of clubs' @mock_channel = Object.new allow( @mock_channel ).to receive( :on_data ).and_yield(nil, @data) @mock_output = Object.new @mock_output_stdout = Object.new @mock_output_output = Object.new allow( @mock_output ).to receive( :stdout ) { @mock_output_stdout } allow( @mock_output ).to receive( :output ) { @mock_output_output } allow( @mock_output_stdout ).to receive( :<< ) allow( @mock_output_output ).to receive( :<< ) end it 'puts data into stdout & output correctly' do expect( @mock_output_stdout ).to receive( :<< ).with(@data) expect( @mock_output_output ).to receive( :<< ).with(@data) connection.register_stdout_for @mock_channel, @mock_output end it 'calls the callback if given' do @mock_callback = Object.new expect( @mock_callback ).to receive( :[] ).with(@data) connection.register_stdout_for @mock_channel, @mock_output, @mock_callback end end describe '#register_stderr_for' do let( :result ) { Beaker::Result.new('hostname', 'command') } before :each do @mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { @mock_ssh } connection.connect @data = '3 of spades' @mock_channel = Object.new allow( @mock_channel ).to receive( :on_extended_data ).and_yield(nil, 1, @data) end it 'puts data into stderr & output correctly' do expect( result.stderr ).to receive( :<< ).with(@data) expect( result.output ).to receive( :<< ).with(@data) connection.register_stderr_for @mock_channel, result end it 'calls the callback if given' do @mock_callback = Object.new expect( @mock_callback ).to receive( :[] ).with(@data) connection.register_stderr_for @mock_channel, result, @mock_callback end it 'skips everything if type is not 1' do allow( @mock_channel ).to receive( :on_extended_data ).and_yield(nil, '1', @data) @mock_callback = Object.new expect( @mock_callback ).to_not receive( :[] ) expect( result.stderr ).to_not receive( :<< ) expect( result.output ).to_not receive( :<< ) connection.register_stderr_for @mock_channel, result, @mock_callback end end describe '#register_exit_code_for' do let( :result ) { Beaker::Result.new('hostname', 'command') } it 'assigns the output\'s exit code correctly from the data' do mock_ssh = Object.new expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { mock_ssh } connection.connect data = '10 of jeromes' mock_data = Object.new allow( mock_data ).to receive( :read_long ) { data } mock_channel = Object.new allow( mock_channel ).to receive( :on_request ).with('exit-status').and_yield(nil, mock_data) connection.register_exit_code_for mock_channel, result expect( result.exit_code ).to be === data end end describe 'process_stdin_for' do it 'calls the correct channel methods in order' do stdin = 'jean shorts' mock_channel = Object.new expect( mock_channel ).to receive( :send_data ).with(stdin).ordered.once expect( mock_channel ).to receive( :process ).ordered.once expect( mock_channel ).to receive( :eof! ).ordered.once connection.process_stdin_for mock_channel, stdin end end describe '#scp_to' do before :each do @mock_ssh = Object.new @mock_scp = Object.new allow( @mock_scp ).to receive( :upload! ) allow( @mock_ssh ).to receive( :scp ).and_return( @mock_scp ) expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { @mock_ssh } connection.connect end it 'calls scp.upload!' do expect( @mock_scp ).to receive( :upload! ).once connection.scp_to '', '' end it 'ensures the connection closes when scp.upload! errors' do expect( @mock_scp ).to receive( :upload! ).once.and_raise(RuntimeError) expect(connection).to receive(:close).once connection.scp_to '', '' end it 'returns a result object' do expect( connection.scp_to '', '' ).to be_a_kind_of Beaker::Result end end describe '#scp_from' do before :each do @mock_ssh = Object.new @mock_scp = Object.new allow( @mock_scp ).to receive( :download! ) allow( @mock_ssh ).to receive( :scp ).and_return( @mock_scp ) expect( Net::SSH ).to receive( :start ).with( ip, user, ssh_opts) { @mock_ssh } connection.connect end it 'calls scp.download!' do expect( @mock_scp ).to receive( :download! ).once connection.scp_from '', '' end it 'ensures the connection closes when scp.download! errors' do expect( @mock_scp ).to receive( :download! ).once.and_raise(RuntimeError) expect(connection).to receive(:close).once connection.scp_from '', '' end it 'returns a result object' do expect( connection.scp_from '', '' ).to be_a_kind_of Beaker::Result end end end end beaker-4.30.0/spec/beaker/subcommand/000077500000000000000000000000001407603575700173605ustar00rootroot00000000000000beaker-4.30.0/spec/beaker/subcommand/subcommand_util_spec.rb000066400000000000000000000077221407603575700241140ustar00rootroot00000000000000require 'spec_helper' module Beaker module Subcommands describe SubcommandUtil do let(:cli) { double("cli") } let(:rake) { double("rake") } let(:file) { double("file") } let(:store) { double("store") } let(:host) { double("host") } let(:hypervisors) { double("hypervisors") } let(:hosts) { double("hosts") } let(:hypervisors_object) { double("hypervisors_object") } let(:hosts_object) { double("hosts_object") } let(:network_manager){ double("network_manager") } let(:save_object){ double("save_object") } let(:load_object){ double("load_object") } let(:yaml_object){ double("yaml_object") } describe 'execute_subcommand' do it "determines if we should execute the init subcommand" do expect(subject.execute_subcommand?("init")).to be == true end it "does not attempt to execute intialize as a subcommand" do expect(subject.execute_subcommand?("initialize")).to be == false end it "determines if we should execute the help subcommand" do expect(subject.execute_subcommand?("help")).to be == true end it "determines if we should execute the provision subcommand" do expect(subject.execute_subcommand?("provision")).to be == true end it "determines that a subcommand should not be executed" do expect(subject.execute_subcommand?("notasubcommand")).to be == false end end describe 'error_with' do it "the exit value should default to 1" do expect(STDOUT).to receive(:puts).with("exiting").exactly(1).times begin subject.error_with("exiting") rescue SystemExit=>e expect(e.status).to eq(1) end end it "the exit value should return specified value" do expect(STDOUT).to receive(:puts).with("exiting").exactly(1).times begin subject.error_with("exiting", {exit_code: 3}) rescue SystemExit=>e expect(e.status).to eq(3) end end it "the exit value should default to 1 with a stack trace" do expect(STDOUT).to receive(:puts).with("exiting").exactly(1).times expect(STDOUT).to receive(:puts).with("testing").exactly(1).times begin subject.error_with("exiting", {stack_trace: "testing"}) rescue SystemExit=>e expect(e.status).to eq(1) end end end describe 'prune_unpersisted' do let(:good_options) do { user: 'root', roles: ['agent'] } end let(:bad_options) do { logger: Beaker::Logger.new, timestamp: Time.now } end let(:initial_options) do Beaker::Options::OptionsHash.new.merge(good_options.merge(bad_options)) end it 'removes unwanted keys from an options hash' do result = subject.prune_unpersisted(initial_options) good_options.keys.each { |key| expect(result).to have_key(key) } bad_options.keys.each { |key| expect(result).not_to have_key(key) } end it 'recurses to remove any nested unwanted keys' do opts = initial_options.merge(child: initial_options.merge(child: initial_options)) result = subject.prune_unpersisted(opts) good_options.keys.each do |key| expect(result).to have_key(key) expect(result[:child]).to have_key(key) expect(result[:child][:child]).to have_key(key) end bad_options.keys.each do |key| expect(result).not_to have_key(key) expect(result[:child]).not_to have_key(key) expect(result[:child][:child]).not_to have_key(key) end end end end end end beaker-4.30.0/spec/beaker/subcommand_spec.rb000066400000000000000000000314671407603575700207320ustar00rootroot00000000000000require 'spec_helper' module Beaker SubcommandUtil = Beaker::Subcommands::SubcommandUtil describe Subcommand do let( :subcommand ) { Beaker::Subcommand.new } context '#initialize' do it 'creates a cli object' do expect(subcommand.cli).to be end describe 'File operation initialization for subcommands' do it 'checks to ensure subcommand file resources exist' do expect(FileUtils).to receive(:mkdir_p).with(SubcommandUtil::CONFIG_DIR) expect(SubcommandUtil::SUBCOMMAND_OPTIONS).to receive(:exist?).and_return(true) expect(SubcommandUtil::SUBCOMMAND_STATE).to receive(:exist?).and_return(true) subcommand end it 'touches the files when they do not exist' do expect(FileUtils).to receive(:mkdir_p).with(SubcommandUtil::CONFIG_DIR) allow(SubcommandUtil::SUBCOMMAND_OPTIONS).to receive(:exist?).and_return(false) allow(SubcommandUtil::SUBCOMMAND_STATE).to receive(:exist?).and_return(false) expect(FileUtils).to receive(:touch).with(SubcommandUtil::SUBCOMMAND_OPTIONS) expect(FileUtils).to receive(:touch).with(SubcommandUtil::SUBCOMMAND_STATE) subcommand end end end context 'ensure that beaker options can be passed through' do let (:beaker_options_list) { [ 'options-file', 'helper', 'load-path', 'tests', 'pre-suite', 'post-suite', 'pre-cleanup', 'provision', 'preserve-hosts', 'preserve-state', 'root-keys', 'keyfile', 'timeout', 'install', 'modules', 'quiet', 'color', 'color-host-output', 'log-level', 'log-prefix', 'dry-run', 'fail-mode', 'ntp', 'repo-proxy', 'add-el-extras', 'package-proxy', 'validate', 'collect-perf-data', 'parse-only', 'tag', 'exclude-tags', 'xml-time-order', 'debug-errors', 'exec_manual_tests', 'test-tag-exclude', 'test-tag-and', 'test-tag-or', 'xml', 'type', 'debug', ] } let( :yaml_store_mock ) { double('yaml_store_mock') } it 'should not error with valid beaker options' do beaker_options_list.each do |option| allow_any_instance_of(Beaker::CLI).to receive(:parse_options) allow_any_instance_of(Beaker::CLI).to receive(:configured_options).and_return({}) allow(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_STATE).and_return(yaml_store_mock) allow(yaml_store_mock).to receive(:transaction).and_yield allow(yaml_store_mock).to receive(:[]=).with('provisioned', false) allow(File).to receive(:open) allow_any_instance_of(Beaker::Logger).to receive(:notify).twice expect(SubcommandUtil::SUBCOMMAND_OPTIONS).to receive(:exist?).and_return(true) expect(SubcommandUtil::SUBCOMMAND_STATE).to receive(:exist?).and_return(true) expect {Beaker::Subcommand.start(['init', '--hosts', 'centos', "--#{option}"])}.to_not output(/ERROR/).to_stderr end end it "should error with a bad option here" do allow(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_STATE).and_return(yaml_store_mock) allow(yaml_store_mock).to receive(:transaction).and_yield allow(yaml_store_mock).to receive(:[]=).with('provisioned', false) expect(File).not_to receive(:open) expect(SubcommandUtil::SUBCOMMAND_OPTIONS).to receive(:exist?).and_return(true) expect(SubcommandUtil::SUBCOMMAND_STATE).to receive(:exist?).and_return(true) expect {Beaker::Subcommand.start(['init', '--hosts', 'centos', '--bad-option'])}.to output(/ERROR/).to_stderr end end context '#init' do let( :cli ) { subcommand.cli } let( :mock_options ) { {:timestamp => 'noon', :other_key => 'cordite'}} let( :yaml_store_mock ) { double('yaml_store_mock') } before :each do allow(cli).to receive(:parse_options) allow(cli).to receive(:configured_options).and_return(mock_options) end it 'calculates options and writes them to disk and deletes the' do allow(File).to receive(:open) allow(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_STATE).and_return(yaml_store_mock) allow(yaml_store_mock).to receive(:transaction).and_yield expect(yaml_store_mock).to receive(:[]=).with('provisioned', false) subcommand.init expect(mock_options).not_to have_key(:timestamp) end it 'requires hosts flag' do expect{subcommand.init}.to raise_error(NotImplementedError) end end context '#provision' do let ( :cli ) { subcommand.cli } let( :yaml_store_mock ) { double('yaml_store_mock') } let ( :host_hash ) { {'mynode.net' => {:name => 'mynode', :platform => Beaker::Platform.new('centos-6-x86_64')}}} let ( :cleaned_hosts ) {double()} let ( :yielded_host_hash ) {double()} let ( :yielded_host_name) {double()} let ( :network_manager) {double('network_manager')} let ( :hosts) {double('hosts')} let ( :hypervisors) {double('hypervisors')} let (:options) {double ('options')} it 'provisions the host and saves the host info' do expect(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_STATE).and_return(yaml_store_mock) allow(yaml_store_mock).to receive(:[]).and_return(false) allow(cli).to receive(:preserve_hosts_file).and_return("/path/to/ho") allow(cli).to receive(:network_manager).and_return(network_manager) allow(cli).to receive(:options).and_return(options) allow(options).to receive(:[]).with(:hosts_preserved_yaml_file).and_return("/path/to/hosts") allow(network_manager).to receive(:hosts).and_return(hosts) allow(network_manager).to receive(:hypervisors).and_return(hypervisors) expect(cli).to receive(:parse_options).and_return(cli) expect(cli).to receive(:provision) expect(cli).to receive(:combined_instance_and_options_hosts).and_return(host_hash) expect(SubcommandUtil).to receive(:sanitize_options_for_save).and_return(cleaned_hosts) expect(cleaned_hosts).to receive(:each).and_yield(yielded_host_name, yielded_host_hash) expect(yielded_host_hash).to receive(:[]=).with('provision', false) expect(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_OPTIONS).and_return(yaml_store_mock) expect(yaml_store_mock).to receive(:transaction).and_yield.exactly(3).times expect(yaml_store_mock).to receive(:[]=).with('HOSTS', cleaned_hosts) expect(yaml_store_mock).to receive(:[]=).with('hosts_preserved_yaml_file', "/path/to/hosts") expect(yaml_store_mock).to receive(:[]=).with('provisioned', true) subcommand.provision end it 'does not allow hosts to be passed' do subcommand.options = {:hosts => "myhost"} expect{subcommand.provision()}.to raise_error(NotImplementedError) end end context 'exec' do before :each do allow(subcommand.cli).to receive(:parse_options) allow(subcommand.cli).to receive(:initialize_network_manager) allow(subcommand.cli).to receive(:execute!) end it 'calls execute! when no resource is given' do expect_any_instance_of(Pathname).to_not receive(:directory?) expect_any_instance_of(Pathname).to_not receive(:exist?) expect(subcommand.cli).to receive(:execute!).once expect{subcommand.exec}.to_not raise_error end it 'allows hard coded suite names to be specified' do subcommand.cli.options[:pre_suite] = %w[step1.rb] subcommand.cli.options[:post_suite] = %w[step2.rb] subcommand.cli.options[:tests] = %w[tests/1.rb] subcommand.exec('pre-suite,tests') expect(subcommand.cli.options[:pre_suite]).to eq(%w[step1.rb]) expect(subcommand.cli.options[:post_suite]).to eq([]) expect(subcommand.cli.options[:tests]).to eq(%w[tests/1.rb]) end it 'errors when a resource is neither a valid file resource or suite name' do allow_any_instance_of(Pathname).to receive(:exist?).and_return(false) expect{subcommand.exec('blahblahblah')}.to raise_error(ArgumentError) end it 'accepts a tests directory, clearing all other suites' do allow_any_instance_of(Pathname).to receive(:exist?).and_return(true) allow_any_instance_of(Pathname).to receive(:directory?).and_return(true) allow(Dir).to receive(:glob) .with('tests/**/*.rb') .and_return(%w[tests/a.rb tests/b/c.rb]) subcommand.exec('tests') expect(subcommand.cli.options[:pre_suite]).to eq([]) expect(subcommand.cli.options[:post_suite]).to eq([]) expect(subcommand.cli.options[:pre_cleanup]).to eq([]) expect(subcommand.cli.options[:tests]).to eq(%w[tests/a.rb tests/b/c.rb]) end it 'accepts comma-separated list of tests, clearing all other suites' do allow_any_instance_of(Pathname).to receive(:exist?).and_return(true) allow_any_instance_of(Pathname).to receive(:file?).and_return(true) subcommand.exec('tests/1.rb,tests/2.rb') expect(subcommand.cli.options[:pre_suite]).to eq([]) expect(subcommand.cli.options[:post_suite]).to eq([]) expect(subcommand.cli.options[:pre_cleanup]).to eq([]) expect(subcommand.cli.options[:tests]).to eq(%w[tests/1.rb tests/2.rb]) end it 'accepts comma-separated list of directories, recursively scanning each' do allow_any_instance_of(Pathname).to receive(:exist?).and_return(true) allow_any_instance_of(Pathname).to receive(:directory?).and_return(true) allow(Dir).to receive(:glob).with('tests/a/**/*.rb').and_return(%w[tests/a/x.rb]) allow(Dir).to receive(:glob).with('tests/b/**/*.rb').and_return(%w[tests/b/x/y.rb tests/b/x/z.rb]) subcommand.exec('tests/a,tests/b') expect(subcommand.cli.options[:tests]).to eq(%w[tests/a/x.rb tests/b/x/y.rb tests/b/x/z.rb]) end it 'rejects comma-separated file and suite name' do allow_any_instance_of(Pathname).to receive(:exist?).and_return(false) expect { subcommand.exec('pre-suite,tests/whoops') }.to raise_error(ArgumentError, %r{Unable to parse pre-suite,tests/whoops}) end let( :yaml_store_mock ) { double('yaml_store_mock') } let( :host_hash ) { {'mynode.net' => {:name => 'mynode', :platform => Beaker::Platform.new('centos-6-x86_64')}}} let( :cleaned_hosts ) {double()} it 'updates the subcommand_options file with new host info if `preserve-state` is set' do allow(yaml_store_mock).to receive(:[]).and_return(false) allow(subcommand).to receive(:options).and_return('preserve-state' => true) expect(subcommand.cli).to receive(:parse_options).and_return(subcommand.cli) expect(subcommand.cli).to receive(:combined_instance_and_options_hosts).and_return(host_hash) expect(SubcommandUtil).to receive(:sanitize_options_for_save).and_return(cleaned_hosts) expect(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_OPTIONS).and_return(yaml_store_mock) expect(yaml_store_mock).to receive(:transaction).and_yield.once expect(yaml_store_mock).to receive(:[]=).with('HOSTS', cleaned_hosts) expect(subcommand.cli.logger).to receive(:notify) subcommand.exec('tests') end it 'does not attempt preserve state if the flag is not passed in' do subcommand.exec('tests') expect(SubcommandUtil).to receive(:sanitize_options_for_save).never expect(subcommand.cli.options['preserve-state']).to be_nil end end context 'destroy' do let( :cli ) { subcommand.cli } let( :mock_options ) { {:timestamp => 'noon', :other_key => 'cordite'}} let( :yaml_store_mock ) { double('yaml_store_mock') } let( :network_manager) {double('network_manager')} it 'calls destroy and updates the yaml store' do allow(cli).to receive(:parse_options) allow(cli).to receive(:initialize_network_manager) allow(cli).to receive(:network_manager).and_return(network_manager) expect(network_manager).to receive(:cleanup) expect(YAML::Store).to receive(:new).with(SubcommandUtil::SUBCOMMAND_STATE).and_return(yaml_store_mock) allow(yaml_store_mock).to receive(:transaction).and_yield allow(yaml_store_mock).to receive(:[]).with('provisioned').and_return(true) allow(yaml_store_mock).to receive(:delete).with('provisioned').and_return(true) expect(SubcommandUtil).to receive(:error_with).with("Please provision an environment").exactly(0).times subcommand.destroy end end end end beaker-4.30.0/spec/beaker/test_case_spec.rb000066400000000000000000000123221407603575700205410ustar00rootroot00000000000000require 'spec_helper' module Beaker describe TestCase do let(:logger) { double('logger').as_null_object } let(:path) { @path || '/tmp/nope' } let(:testcase) { TestCase.new({}, logger, {}, path) } context 'run_test' do it 'defaults to test_status :pass on success' do path = 'test.rb' File.open(path, 'w') do |f| f.write "" end @path = path expect( testcase ).to_not receive( :log_and_fail_test ) testcase.run_test status = testcase.instance_variable_get(:@test_status) expect(status).to be === :pass end it 'updates test_status to :skip on SkipTest' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise SkipTest" end @path = path expect( testcase ).to_not receive( :log_and_fail_test ) testcase.run_test status = testcase.instance_variable_get(:@test_status) expect(status).to be === :skip end it 'updates test_status to :pending on PendingTest' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise PendingTest" end @path = path expect( testcase ).to_not receive( :log_and_fail_test ) testcase.run_test status = testcase.instance_variable_get(:@test_status) expect(status).to be === :pending end it 'updates test_status to :fail on FailTest' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise FailTest" end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Beaker::DSL::FailTest), :fail).and_call_original testcase.run_test status = testcase.instance_variable_get(:@test_status) expect(status).to be === :fail end it 'correctly handles RuntimeError' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise RuntimeError" end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(RuntimeError)) testcase.run_test end it 'correctly handles ScriptError' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise ScriptError" end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(ScriptError)) testcase.run_test end it 'correctly handles Timeout::Error' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise Timeout::Error" end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Timeout::Error)) testcase.run_test end it 'correctly handles CommandFailure' do path = 'test.rb' File.open(path, 'w') do |f| f.write "raise Host::CommandFailure" end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Host::CommandFailure)) testcase.run_test end it 'records a test failure if an assertion fails in a teardown block' do path = 'test.rb' File.open(path, 'w') do |f| f.write <<-EOF teardown do assert_equal(1, 2, 'Oh noes!') end EOF end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Minitest::Assertion), :teardown_error).and_call_original testcase.run_test expect @test_status == :error end it 'does not overwrite a test failure if an assertion also happens in a teardown block' do path = 'test.rb' File.open(path, 'w') do |f| f.write <<-EOF teardown do assert_equal(1, 2, 'Oh noes!') end assert_equal(true, false, 'failed test') EOF end @path = path expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Minitest::Assertion), :fail).and_call_original expect( testcase ).to receive( :log_and_fail_test ).once.with(kind_of(Minitest::Assertion), :teardown_error).and_call_original testcase.run_test expect @test_status == :fail end end context 'metadata' do it 'sets the filename correctly from the path' do answer = 'jacket' path = "#{answer}.rb" File.open(path, 'w') do |f| f.write "" end @path = path testcase.run_test metadata = testcase.instance_variable_get(:@metadata) expect(metadata[:case][:file_name]).to be === answer end it 'resets the step name' do path = 'test.rb' File.open(path, 'w') do |f| f.write "" end @path = path # we have to create a TestCase by hand, so that we can set old tc = TestCase.new({}, logger, {}, path) # metadata on it, so that we can test that it's being reset correctly old_metadata = { :step => { :name => 'CharlieBrown' } } tc.instance_variable_set(:@metadata, old_metadata) tc.run_test metadata = tc.instance_variable_get(:@metadata) expect(metadata[:step][:name]).to be_nil end end end end beaker-4.30.0/spec/beaker/test_suite_spec.rb000066400000000000000000000403711407603575700207640ustar00rootroot00000000000000require 'spec_helper' require 'fileutils' module Beaker describe TestSuite do context 'new' do let(:test_dir) { 'tmp/tests' } let(:options) { {'name' => create_files(@files)} } let(:rb_test) { File.expand_path(test_dir + '/my_ruby_file.rb') } let(:pl_test) { File.expand_path(test_dir + '/my_perl_file.pl') } let(:sh_test) { File.expand_path(test_dir + '/my_shell_file.sh') } it 'fails without test files' do expect { Beaker::TestSuite.new('name', 'hosts', Hash.new, Time.now, :stop_on_error) }.to raise_error end it 'includes specific files as test file when explicitly passed' do @files = [ rb_test ] ts = Beaker::TestSuite.new('name', 'hosts', options, Time.now, :stop_on_error) tfs = ts.instance_variable_get(:@test_files) expect(tfs).to include rb_test end it 'defaults to :slow fail_mode if not provided through parameter or options' do @files = [ rb_test ] ts = Beaker::TestSuite.new('name', 'hosts', options, Time.now) tfm = ts.instance_variable_get(:@fail_mode) expect(tfm).to be == :slow end it 'uses provided parameter fail_mode' do @files = [ rb_test ] ts = Beaker::TestSuite.new('name', 'hosts', options, Time.now, :fast) tfm = ts.instance_variable_get(:@fail_mode) expect(tfm).to be == :fast end it 'uses options fail_mode if fail_mode parameter is not provided' do @files = [ rb_test ] options[:fail_mode] = :fast ts = Beaker::TestSuite.new('name', 'hosts', options, Time.now) tfm = ts.instance_variable_get(:@fail_mode) expect(tfm).to be == :fast end end context 'run' do let( :options ) { make_opts.merge({ :logger => double().as_null_object, 'name' => create_files(@files), :log_dated_dir => '.', :xml_dated_dir => '.'}) } let(:broken_script) { "raise RuntimeError" } let(:fail_script) { "raise Beaker::DSL::Outcomes::FailTest" } let(:okay_script) { "true" } let(:rb_test) { 'my_ruby_file.rb' } let(:pl_test) { '/my_perl_file.pl' } let(:sh_test) { '/my_shell_file.sh' } let(:hosts) { make_hosts() } it 'fails fast if fail_mode != :slow and runtime error is raised' do allow( Logger ).to receive('new') @files = [ rb_test, pl_test, sh_test] File.open(rb_test, 'w') { |file| file.write(broken_script) } File.open(pl_test, 'w') { |file| file.write(okay_script) } File.open(sh_test, 'w') { |file| file.write(okay_script) } ts = Beaker::TestSuite.new( 'name', hosts, options, Time.now, :stop ) tsr = ts.instance_variable_get( :@test_suite_results ) allow( tsr ).to receive(:write_junit_xml).and_return( true ) allow( tsr ).to receive(:summarize).and_return( true ) ts.run expect( tsr.errored_tests ).to be === 1 expect( tsr.failed_tests ).to be === 0 expect( tsr.test_count ).to be === 1 expect( tsr.passed_tests).to be === 0 end it 'fails fast if fail_mode != :slow and fail test is raised' do allow( Logger ).to receive('new') @files = [ rb_test, pl_test, sh_test] File.open(rb_test, 'w') { |file| file.write(fail_script) } File.open(pl_test, 'w') { |file| file.write(okay_script) } File.open(sh_test, 'w') { |file| file.write(okay_script) } ts = Beaker::TestSuite.new( 'name', hosts, options, Time.now, :stop ) tsr = ts.instance_variable_get( :@test_suite_results ) allow( tsr ).to receive(:write_junit_xml).and_return( true ) allow( tsr ).to receive(:summarize).and_return( true ) ts.run expect( tsr.errored_tests ).to be === 0 expect( tsr.failed_tests ).to be === 1 expect( tsr.test_count ).to be === 1 expect( tsr.passed_tests).to be === 0 end it 'fails slow if fail_mode = :slow, even if a test fails and there is a runtime error' do allow( Logger ).to receive('new') @files = [ rb_test, pl_test, sh_test] File.open(rb_test, 'w') { |file| file.write(broken_script) } File.open(pl_test, 'w') { |file| file.write(fail_script) } File.open(sh_test, 'w') { |file| file.write(okay_script) } ts = Beaker::TestSuite.new( 'name', hosts, options, Time.now, :slow ) tsr = ts.instance_variable_get( :@test_suite_results ) allow( tsr ).to receive(:write_junit_xml).and_return( true ) allow( tsr ).to receive(:summarize).and_return( true ) ts.run expect( tsr.errored_tests ).to be === 1 expect( tsr.failed_tests ).to be === 1 expect( tsr.test_count ).to be === 3 expect( tsr.passed_tests).to be === 1 end end describe TestSuiteResult do let( :options ) { make_opts.merge({ :logger => double().as_null_object }) } let( :hosts ) { make_hosts() } let( :testcase1 ) { Beaker::TestCase.new( hosts, options[:logger], options) } let( :testcase2 ) { Beaker::TestCase.new( hosts, options[:logger], options) } let( :testcase3 ) { Beaker::TestCase.new( hosts, options[:logger], options) } let( :test_suite_result ) { TestSuiteResult.new( options, "my_suite") } it 'supports adding test cases' do expect( test_suite_result.test_count ).to be === 0 test_suite_result.add_test_case( testcase1 ) expect( test_suite_result.test_count ).to be === 1 end it 'calculates passed tests' do testcase1.instance_variable_set(:@test_status, :pass) testcase2.instance_variable_set(:@test_status, :pass) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.passed_tests ).to be == 2 end it 'calculates failed tests' do testcase1.instance_variable_set(:@test_status, :pass) testcase2.instance_variable_set(:@test_status, :pass) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.failed_tests ).to be == 1 end it 'calculates errored tests' do testcase1.instance_variable_set(:@test_status, :error) testcase2.instance_variable_set(:@test_status, :pass) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.errored_tests ).to be == 1 end it 'calculates skipped tests' do testcase1.instance_variable_set(:@test_status, :error) testcase2.instance_variable_set(:@test_status, :skip) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.skipped_tests ).to be == 1 end it 'calculates pending tests' do testcase1.instance_variable_set(:@test_status, :error) testcase2.instance_variable_set(:@test_status, :pending) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.pending_tests ).to be == 1 end it 'calculates sum_failed as a sum of errored and failed TestCases' do testcase1.instance_variable_set(:@test_status, :error) testcase2.instance_variable_set(:@test_status, :pending) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.sum_failed ).to be == 2 end it 'reports success with no errors/failures' do testcase1.instance_variable_set(:@test_status, :pass) testcase2.instance_variable_set(:@test_status, :pending) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.success? ).to be == false end it 'reports failed if any tests error/fail' do testcase1.instance_variable_set(:@test_status, :pass) testcase2.instance_variable_set(:@test_status, :pending) testcase3.instance_variable_set(:@test_status, :fail) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.failed? ).to be == true end it 'can calculate the sum of all TestCase runtimes' do testcase1.instance_variable_set(:@runtime, 1) testcase2.instance_variable_set(:@runtime, 10) testcase3.instance_variable_set(:@runtime, 100) test_suite_result.add_test_case( testcase1 ) test_suite_result.add_test_case( testcase2 ) test_suite_result.add_test_case( testcase3 ) expect( test_suite_result.elapsed_time ).to be == 111 end describe '#print_test_result' do it 'prints the test result without the line number if no file path' do tc = Beaker::TestCase.new( hosts, options[:logger], options) ex = StandardError.new('failed') allow(ex).to receive(:backtrace).and_return(['path_to_test_file.rb line 1 - blah']) tc.instance_variable_set(:@exception, ex) test_suite_result.add_test_case( tc ) expect(test_suite_result.print_test_result(tc)).not_to match(/Test line:/) expect{ test_suite_result.print_test_result(tc) }.to_not raise_error end it 'prints the test result and line number from test case file on failure' do tc = Beaker::TestCase.new( hosts, options[:logger], options, 'path_to_test_file.rb') ex = StandardError.new('failed') allow(ex).to receive(:backtrace).and_return(['path_to_test_file.rb line 1 - blah']) tc.instance_variable_set(:@exception, ex) test_suite_result.add_test_case( tc ) expect(test_suite_result.print_test_result(tc)).to match(/Test line:/) expect{ test_suite_result.print_test_result(tc) }.to_not raise_error end end describe '#write_junit_xml' do let(:options) { make_opts.merge({:logger => double().as_null_object, 'name' => create_files(@files), :log_dated_dir => '.', :xml_dated_dir => '.'}) } let(:rb_test) { 'my_ruby_file.rb' } before(:each) do @files = [ rb_test, rb_test, rb_test] @ts = Beaker::TestSuite.new( 'name', hosts, options, Time.now, :fast ) @tsr = @ts.instance_variable_get( :@test_suite_results ) allow( @tsr ).to receive( :start_time ).and_return(0) allow( @tsr ).to receive( :stop_time ).and_return(10) @test_cases = [] @files.each_with_index do |file, index| tc = Beaker::TestCase.new( hosts, options[:logger], options, rb_test) allow( tc ).to receive( :sublog ).and_return( false ) @test_cases << tc end @rexml_mock = REXML::Element.new("testsuites") allow(REXML::Element).to receive( :add_element ).and_call_original allow( LoggerJunit ).to receive( :write_xml ).and_yield( Object.new, @rexml_mock ) end it 'doesn\'t re-order test cases themselves on time_sort' do expect( @tsr.instance_variable_get( :@logger ) ).to receive( :error ).never @test_cases.each_with_index do |tc,index| tc.instance_variable_set(:@runtime, 3**index) @tsr.add_test_case( tc ) end original_testcase_order = test_suite_result.instance_variable_get( :@test_cases ).dup time_sort = true @tsr.write_junit_xml( 'fakeFilePath07', 'fakeFileToLink09', time_sort ) after_testcase_order = test_suite_result.instance_variable_get( :@test_cases ).dup expect( after_testcase_order ).to be === original_testcase_order end it 'writes @export nested hashes properly' do expect( @tsr.instance_variable_get( :@logger ) ).to receive( :error ).never inner_value = {'second' => '2nd'} @test_cases.each do |tc| tc.instance_variable_set(:@runtime, 0) tc.instance_variable_set(:@exports, [{'oh hey' => 'hai', 'first' => inner_value}]) @tsr.add_test_case( tc ) end @tsr.write_junit_xml( 'fakeFilePath08') @rexml_mock.elements.each("//testcase") do |e| expect(e.attributes["oh_hey"].to_s).to eq('hai') expect(e.attributes["first"]).to eq(inner_value.to_s) end end it 'writes @export array of hashes properly' do expect( @tsr.instance_variable_get( :@logger ) ).to receive( :error ).never @test_cases.each do |tc| tc.instance_variable_set(:@runtime, 0) tc.instance_variable_set(:@exports, [{:yes => 'hello'}, {:uh => 'sher'}]) @tsr.add_test_case( tc ) end @tsr.write_junit_xml( 'fakeFilePath08' ) @rexml_mock.elements.each("//testcase") do |e| expect(e.attributes["yes"].to_s).to eq('hello') expect(e.attributes["uh"].to_s).to eq('sher') end end it 'writes @export hashes per test case properly' do expect( @tsr.instance_variable_get( :@logger ) ).to receive( :error ).never @test_cases.each_with_index do |tc,index| tc.instance_variable_set(:@runtime, 0) tc.instance_variable_set(:@exports, [{"yes_#{index}" => "hello#{index}"}]) @tsr.add_test_case( tc ) end @tsr.write_junit_xml( 'fakeFilePath08' ) index = 0 @rexml_mock.elements.each("//testcase") do |e| expect(e.attributes["yes_#{index}"]).to eq("hello#{index}") index += 1 end end end end describe '#log_path' do let( :sh_test ) { '/my_shell_file.sh' } let( :files ) { @files ? @files : [sh_test] } let( :options ) { make_opts.merge({ :logger => double().as_null_object, 'name' => create_files(files) }) } let( :hosts ) { make_hosts() } let( :testsuite ) { Beaker::TestSuite.new( 'name', hosts, options, Time.now, :stop ) } it 'returns the simple joining of the log dir & file as required' do expect(testsuite.log_path('foo.txt', 'man/date')).to be === 'man/date/foo.txt' end describe 'builds the base directory correctly' do # the base directory is where the latest symlink itself should live it 'in the usual case' do expect( File.symlink?('man/latest') ).to be_falsy testsuite.log_path('foo.txt', 'man/date') expect( File.symlink?('man/latest') ).to be_truthy end it 'if given a nested directory' do expect( File.symlink?('a/latest') ).to be_falsy testsuite.log_path('foo.txt', 'a/b/c/d/e/f') expect( File.symlink?('a/latest') ).to be_truthy end end describe 'builds the symlink directory correctly' do # the symlink directory is where the symlink points to it 'in the usual case' do expect( File.symlink?('d/latest') ).to be_falsy testsuite.log_path('foo.txt', 'd/e') expect( File.readlink('d/latest') ).to be === 'e' end it 'if given a nested directory' do expect( File.symlink?('f/latest') ).to be_falsy testsuite.log_path('foo.txt', 'f/g/h/i/j/k') expect( File.readlink('f/latest') ).to be === 'g/h/i/j/k' end end end end end beaker-4.30.0/spec/helpers.rb000066400000000000000000000140671407603575700157760ustar00rootroot00000000000000module TestFileHelpers def create_files file_array file_array.each do |f| FileUtils.mkdir_p File.dirname(f) FileUtils.touch f end end def fog_file_contents { :default => { :aws_access_key_id => "IMANACCESSKEY", :aws_secret_access_key => "supersekritkey", :aix_hypervisor_server => "aix_hypervisor.labs.net", :aix_hypervisor_username => "aixer", :aix_hypervisor_keyfile => "/Users/user/.ssh/id_rsa-acceptance", :solaris_hypervisor_server => "solaris_hypervisor.labs.net", :solaris_hypervisor_username => "harness", :solaris_hypervisor_keyfile => "/Users/user/.ssh/id_rsa-old.private", :solaris_hypervisor_vmpath => "rpoooool/zs", :solaris_hypervisor_snappaths => ["rpoooool/USER/z0"], :vsphere_server => "vsphere.labs.net", :vsphere_username => "vsphere@labs.com", :vsphere_password => "supersekritpassword"} } end end module HostHelpers HOST_DEFAULTS = { :platform => 'unix', :roles => ['agent'], :snapshot => 'snap', :ip => 'default.ip.address', :private_ip => 'private.ip.address', :dns_name => 'default.box.tld', :box => 'default_box_name', :box_url => 'http://default.box.url', :image => 'default_image', :flavor => 'm1.large', :user_data => '#cloud-config\nmanage_etc_hosts: true\nfinal_message: "The host is finally up!"' } HOST_NAME = "vm%d" HOST_SNAPSHOT = "snapshot%d" HOST_IP = "ip.address.for.%s" HOST_BOX = "vm2%s_of_my_box" HOST_BOX_URL = "http://address.for.my.box.%s" HOST_DNS_NAME = "%s.box.tld" HOST_TEMPLATE = "%s_has_a_template" HOST_PRIVATE_IP = "private.ip.for.%s" def logger double( 'logger' ).as_null_object end def make_opts opts = Beaker::Options::Presets.new opts.presets.merge( opts.env_vars ).merge( { :logger => logger, :host_config => 'sample.config', :type => nil, :pooling_api => 'http://vcloud.delivery.puppetlabs.net/', :datastore => 'instance0', :folder => 'Delivery/Quality Assurance/Staging/Dynamic', :resourcepool => 'delivery/Quality Assurance/Staging/Dynamic', :gce_project => 'beaker-compute', :gce_keyfile => '/path/to/keyfile.p12', :gce_password => 'notasecret', :gce_email => '12345678910@developer.gserviceaccount.com', :openstack_api_key => "P1as$w0rd", :openstack_username => "user", :openstack_auth_url => "http://openstack_hypervisor.labs.net:5000/v2.0/tokens", :openstack_tenant => "testing", :openstack_network => "testing", :openstack_keyname => "nopass", :floating_ip_pool => "my_pool", :security_group => ['my_sg', 'default'] } ) end def generate_result (name, opts ) result = double( 'result' ) stdout = opts.has_key?(:stdout) ? opts[:stdout] : name stderr = opts.has_key?(:stderr) ? opts[:stderr] : name exit_code = opts.has_key?(:exit_code) ? opts[:exit_code] : 0 exit_code = [exit_code].flatten allow( result ).to receive( :stdout ).and_return( stdout ) allow( result ).to receive( :stderr ).and_return( stderr ) allow( result ).to receive( :exit_code ).and_return( *exit_code ) result end def make_host_opts name, opts make_opts.merge( { 'HOSTS' => { name => opts } } ).merge( opts ) end def make_host name, host_hash host_hash = Beaker::Options::OptionsHash.new.merge(HOST_DEFAULTS.merge(host_hash)) host = Beaker::Host.create( name, host_hash, make_opts) allow(host).to receive( :exec ).and_return( generate_result( name, host_hash ) ) allow(host).to receive( :close ) host end def make_hosts preset_opts = {}, amt = 3 hosts = [] (1..amt).each do |num| name = HOST_NAME % num opts = { :snapshot => HOST_SNAPSHOT % num, :ip => HOST_IP % name, :private_ip => HOST_PRIVATE_IP % name, :dns_name => HOST_DNS_NAME % name, :template => HOST_TEMPLATE % name, :box => HOST_BOX % name, :box_url => HOST_BOX_URL % name }.merge( preset_opts ) hosts << make_host(name, opts) end hosts end def make_instance instance_data = {} OpenStruct.new instance_data end end module PlatformHelpers DEBIANPLATFORMS = ['debian', 'ubuntu', 'cumulus', 'huaweios'] FEDORASYSTEMD = (14..39).to_a.collect! { |i| "fedora-#{i}" } SYSTEMDPLATFORMS = ['el-7', 'centos-7', 'redhat-7', 'oracle-7', 'scientific-7', 'eos-7', 'el-8', 'centos-8', 'redhat-8', 'oracle-8'].concat(FEDORASYSTEMD) FEDORASYSTEMV = (1..13).to_a.collect! { |i| "fedora-#{i}" } SYSTEMVPLATFORMS = ['el-', 'centos', 'fedora', 'redhat', 'oracle', 'scientific', 'eos'].concat(FEDORASYSTEMV) end beaker-4.30.0/spec/matchers.rb000066400000000000000000000034211407603575700161320ustar00rootroot00000000000000RSpec::Matchers.define :execute_commands_matching do |pattern| match do |actual| raise(RuntimeError, "Expected #{actual} to be a FakeHost") unless actual.kind_of?(FakeHost::MockedExec) @found_count = actual.command_strings.grep(pattern).size @times.nil? ? @found_count > 0 : @found_count == @times end chain :exactly do |times| @times = times end chain :times do # clarity only end chain :once do @times = 1 end def message(actual, pattern, times, found_count) msg = times == 1 ? "#{pattern} once" : "#{pattern} #{times} times" msg += " but instead found a count of #{found_count}" if found_count != times msg + " in:\n #{actual.command_strings.pretty_inspect}" end failure_message do |actual| "Expected to find #{message(actual, pattern, @times, @found_count)}" end failure_message_when_negated do |actual| "Unexpectedly found #{message(actual, pattern, @times, @found_count)}" end end RSpec::Matchers.define :execute_commands_matching_in_order do |*patterns| match do |actual| raise(RuntimeError, "Expected #{actual} to be a FakeHost") unless actual.kind_of?(FakeHost::MockedExec) remaining_patterns = patterns.clone actual.command_strings.each do |line| if remaining_patterns.empty? break elsif remaining_patterns.first.match(line) remaining_patterns.shift end end remaining_patterns.empty? end def message(actual, patterns) msg = "#{patterns.join(', ')} in order" + " in:\n #{actual.command_strings.pretty_inspect}" end failure_message do |actual| "Expected to find #{message(actual, patterns)}" end failure_message_when_negated do |actual| "Unexpectedly found #{message(actual, patterns)}" end end beaker-4.30.0/spec/mock_fission.rb000066400000000000000000000016561407603575700170170ustar00rootroot00000000000000class Response attr_accessor :code, :message, :data def initialize(code = 0, message = '', data = nil) @code = code @message = message @data = data end end class MockFissionVM attr_accessor :name @@snaps = [] def initialize name @name = name @running = true end def self.set_snapshots snaps @@snaps = snaps end def snapshots Response.new(0, '', @@snaps) end def revert_to_snapshot name @running = false end def running? Response.new(0, '', @running) end def start opt @running = true end def exists? true end end class MockFission @@vms = [] def self.presets hosts snaps = [] hosts.each do |host| @@vms << MockFissionVM.new( host.name ) snaps << host[ :snapshot ] end MockFissionVM.set_snapshots(snaps) end def self.all Response.new(0, '', @@vms) end def self.new name MockFissionVM.new(name) end end beaker-4.30.0/spec/mock_vsphere.rb000066400000000000000000000116471407603575700170220ustar00rootroot00000000000000class MockRbVmomiSnapshot attr_accessor :name attr_accessor :rootSnapshotList attr_accessor :childSnapshotList def initialize @name = nil @rootSnapshotList = [] @childSnapshotList = [] end def print_nested_array arg str = '[ ' arg.each do |arry| if arry.is_a?(Array) str += print_nested_array( arry ) elsif arry.is_a?(MockRbVmomiSnapshot) str += arry.to_s + ", " end end str + ' ]' end def to_s "Snapshot(name: #{@name}, rootSnapshotlist: #{print_nested_array(@rootSnapshotList)}, childSnapshotList: #{print_nested_array(@childSnapshotList)})" end def snapshot self end end class MockRbVmomiVM attr_accessor :snapshot, :name, :state def info self end def process_snaphash snaphash shotlist = [] snaphash.each do | name, subsnaps | new_snap = MockRbVmomiSnapshot.new new_snap.name = name if subsnaps.is_a?(Hash) new_snap.childSnapshotList = process_snaphash( subsnaps ) end shotlist << new_snap end shotlist end def get_snapshot name, snaplist = @snapshot.rootSnapshotList snapshot = nil snaplist.each do |snap| if snap.is_a?(Array) snapshot = get_snapshot(snap, name) elsif snap.name == name snapshot = snap.snapshot end end snapshot end def initialize name, snaphash @name = name @snapshot = MockRbVmomiSnapshot.new @snapshot.name = name @snapshot.rootSnapshotList = process_snaphash( snaphash ) end end class MockRbVmomiConnection class CustomizationSpecManager class CustomizationSpec def spec true end end def initialize @customizationspec = CustomizationSpec.new end def GetCustomizationSpec arg @customizationspec end end class PropertyCollector class Result def initialize(name, object) @name = name @object = object end def val @name end def obj @object end def propSet [self] end end class ResultContainer attr_accessor :token def initialize @results = [] end def objects @results end def add_object obj @results << obj end end def initialize @results = ResultContainer.new end def RetrievePropertiesEx hash @results.token = true @results end def ContinueRetrievePropertiesEx token @results.token = false @results end def add_result name, object @results.add_object( Result.new(name, object) ) end def WaitForUpdates arg result = OpenStruct.new result.version = 'version' result end def CreateFilter arg filter = OpenStruct.new filter.DestroyPropertyFilter = true filter end end class ServiceInstance class Datacenter attr_accessor :vmFolder attr_accessor :hostFolder def initialize @vmFolder = MockRbVmomi::VIM::Folder.new @vmFolder.name = "/root" end def find_datastore arg true end end def initialize @datacenter = Datacenter.new end def find_datacenter dc @datacenter end end class ServiceManager class ViewManager def CreateContainerView hash @view = hash end end def initialize @customizationspecmanager = CustomizationSpecManager.new @viewmanager = ViewManager.new end def customizationSpecManager @customizationspecmanager end def viewManager @viewmanager end def rootFolder "/root" end end def initialize opts @host = opts[ :host ] @user = opts[ :user ] @password = opts[ :password ] @insecure = opts[ :insecure ] @serviceinstance = ServiceInstance.new @servicemanager = ServiceManager.new @propertycollector = PropertyCollector.new end def serviceInstance @serviceinstance end def serviceContent @servicemanager end def propertyCollector @propertycollector end def set_info vms vms.each do |vm| @propertycollector.add_result(vm.name, vm) end end end class MockRbVmomi class VIM class Folder attr_accessor :name def find self end def childEntity self end def resourcePool self end def traverse path, type=Object, create=false self end end class ResourcePool attr_accessor :name def find self end def resourcePool self end end class ClusterComputeResource attr_accessor :name def find self end def resourcePool self end end class TraversalSpec def initialize hash end end def self.connect opts MockRbVmomiConnection.new( opts ) end end end beaker-4.30.0/spec/mock_vsphere_helper.rb000066400000000000000000000053461407603575700203600ustar00rootroot00000000000000class MockVsphereSnapshot attr_accessor :name def RevertToSnapshot_Task self end def wait_for_completion true end end class MockVsphereVM attr_accessor :name, :powerState, :snapshot, :template, :toolsRunningStatus def initialize @powerState = "poweredOff" @toolsRunningStatus = "guestToolsStopped" @first = true end def runtime self end def powerState @powerState end def toolsRunningStatus prev = @toolsRunningStatus @toolsRunningStatus = "guestToolsRunning" prev end def PowerOnVM_Task @powerState = 'poweredOn' self end def PowerOffVM_Task @powerState = "poweredOff" self end def summary self end def guest self end def ipAddress @toolsRunningStatus = 'guestToolsRunning' "#{@name}.ip.address" end def CloneVM_Task opts clone = MockVsphereVM.new clone.name = opts[:name] clone.snapshot = self.snapshot clone.template = self.template clone.PowerOnVM_Task MockVsphereHelper.add_vm( opts[:name], clone ) clone end def wait_for_completion true end def Destroy_Task true end end class MockVsphereHelper @@fog_file = {} @@vms = {} def initialize arg end def self.add_vm name, vm @@vms[name] = vm end def self.set_config conf @@fog_file = conf end def self.powerOn @@vms.each do | name, vm | vm.powerState = "poweredOn" end end def self.powerOff @@vms.each do | name, vm | vm.powerState = "poweredOff" end end def self.set_vms hosts @@vms = {} hosts.each do |host| vm = MockVsphereVM.new vm.name = host.name vm.snapshot = MockVsphereSnapshot.new vm.snapshot.name = host[:snapshot] @@vms[host.name] = vm template = MockVsphereVM.new template.name = host[:template] template.snapshot = MockVsphereSnapshot.new template.snapshot.name = host[:snapshot] @@vms[host[:template]] = template end end def self.load_config file @@fog_file end def find_vms keys found = {} keys = ([] << keys) keys.flatten! keys.each do |key| if @@vms.has_key?( key ) found[key] = @@vms[key] end end found end def self.find_vm key if @@vms.has_key?( key ) @@vms[key] else nil end end def find_snapshot vm, snap if @@vms[vm.name].snapshot.name == snap @@vms[vm.name].snapshot else nil end end def find_customization template nil end def find_datastore dc,datastore datastore end def find_pool dc,pool pool end def find_folder dc,folder folder end def wait_for_tasks tasks, try, attempts true end def close true end end beaker-4.30.0/spec/mocks.rb000066400000000000000000000033421407603575700154420ustar00rootroot00000000000000require 'rspec/mocks' module MockNet class HTTP class Response class ResponseHash def []key if key == "domain" nil else { 'ok' => true, 'hostname' => 'pool' } end end end def body ResponseHash.new end end class Post def initialize uri @uri = uri end def body= *args hash end end class Put def initialize uri @uri = uri end def body= *args hash end end class Delete def initialize uri @uri = uri end end def initialize host, port @host = host @port = port end def request req Response.new end end end module FakeHost include RSpec::Mocks::TestDouble def self.create(name = 'fakevm', platform = 'redhat-version-arch', options = {}) options_hash = Beaker::Options::OptionsHash.new.merge(options) options_hash[:logger] = RSpec::Mocks::Double.new('logger').as_null_object host = Beaker::Host.create(name, { 'platform' => Beaker::Platform.new(platform) } , options_hash) host.extend(MockedExec) host end module MockedExec def self.extended(other) other.instance_eval do send(:instance_variable_set, :@commands, []) end end attr_accessor :commands def port_open?(port) true end def any_exec_result RSpec::Mocks::Double.new('exec-result').as_null_object end def exec(command, options = {}) commands << command any_exec_result end def command_strings commands.map { |c| [c.command, c.args].join(' ') } end end def log_prefix "FakeHost" end end beaker-4.30.0/spec/spec_helper.rb000066400000000000000000000013331407603575700166150ustar00rootroot00000000000000begin require 'simplecov' require 'simplecov-console' require 'codecov' rescue LoadError else SimpleCov.start do track_files 'lib/**/*.rb' add_filter '/spec' enable_coverage :branch # do not track vendored files add_filter '/vendor' add_filter '/.vendor' end SimpleCov.formatters = [ SimpleCov::Formatter::Console, SimpleCov::Formatter::Codecov, ] end require 'beaker' require 'fakefs/spec_helpers' require 'mocks' require 'helpers' require 'matchers' require 'mock_fission' require 'mock_vsphere' require 'mock_vsphere_helper' require 'rspec/its' RSpec.configure do |config| config.include FakeFS::SpecHelpers config.include TestFileHelpers config.include HostHelpers end