PDQTest
PDQTest - Puppet Docker Quick-test - is the quickest and easiest way to test your puppet modules. PDQTest features tests for:
- Linting
- Syntax
- RSpec
- Acceptance (BATS)
And can generate code to retrofit testing to a new or existing module, along with skeleton RSpec and acceptance tests to get you started.
PDQTest runs linting, syntax and RSpec tests within the machine it is running from and then loads a docker container to perform acceptance testing.
Installation
To install PDQTest on your system:
System Ruby
gem install pdqtest
Bundler, add this line to your application's Gemfile:
gem 'pdqtest
- It's advisable to specify a version number to ensure repeatable builds
Puppet modules
To add PDQTests to a new or existing puppet module, run the command:
pdqtest init
This will install PDQTest into the Gemfile
and will generate an example set of acceptance tests
Running tests
PDQTest preparation
PDQTest needs to download a Docker image before running tests:
bundle exec pdqtest setup
This image is updated periodically, the above command will download the appropriate image for the version of pdqtest
being used.
Module dependencies/.fixtures.yml
There is no need to maintain a .fixtures.yml
file and the presence of this file when using pdqtest
is an error.
Public modules (PuppetForge)
Dependencies on public forge modules must be specified in your module's metadata.json
file.
Private modules (from git)
If you need to download modules from git, then you must populate the fixtures
section of fixtures.yml
, eg:
repositories:
corporatestuff:
repo: 'https://nonpublicgit.megacorp.com/corporatestuff.git'
ref: 'mybranch'
Notes:
- The filename is
fixtures.yml
NOT.fixtures.yml
. The leading dot had to be removed to avoidpuppetlabs_spec_helper
also detecting the file and trying to use it. - The file format of
.fixtures.yml
andfixtures.yml
for specifing git repositories is identical - Only the repositories section of the file will be processed as we do not use
puppetlabs_spec_helper
to do this for us.
All tests
If you just want to run all tests:
bundle exec pdqtest all
RSpec tests
bundle exec pdqtest rspec
Debugging failed builds
PDQTest makes it easy to debug failed builds:
pdqtest shell
- Open a shell inside the docker container that would be used to run tests
- Your code is available at
/cut
pdqtest --keep-container all
- Run all tests, report pass/fail status
- Keep the container Running
- After testing, the container used to run tests will be left running and a message will be printed showing how to enter the container used for testing. Your code is avaiable at
/cut
About acceptance tests
- Acceptance tests run within a Docker container managed by PDQTest
- The Docker container breaks all the rules of Docker and runs a full version of Systemd to allow complete testing of Puppet code in the most portable way (basically treat docker as a high speed VM)... This is deliberate - PDQTest exists to get results, not be a perfect Docker app.
- The test container uses a minimal version of Centos 7, there's no option to change this at present
- The container will only be started if at least one example file is present and contains the correct magic marker
- You can get a shell on the docker container if needed:
- A representitive system, identical to that which will be used to run the tests
pdqtest shell
- The actual container that was just used on a test run
pdqtest --keep-container all
and run the command indicated on exit
- A representitive system, identical to that which will be used to run the tests
Test workflow
- Scan for all example files under
/examples
. Files must end in.pp
and contain the magic marker#@PDQTest
to be processed - If example files are present, start the docker container
- For each example file:
- Check for a file at
/spec/acceptance/EXAMPLE_NAME__setup.sh
, If it exists, run it inside the container. This is a useful place to install preqrequisites, edit files, install mock services, clean up after other tests, etc. - Check for a file at
/spec/acceptance/EXAMPLE_NAME__before.bats
, If it exists run BATS against it. This is normally used to check the system state is clean or that any test prerequisites have been setup correctly - Run
puppet apply
on the example file twice - Check for a file at
/spec/acceptance/EXAMPLE_NAME.bats
, If it exists run BATS against it. This is normally used to check the state of the system after running puppet. You can do things like check services are running, check files edited correctly, or basically anything that you can write in bash!
- Check for a file at
- Destroy the container unless we were asked to keep it with
--keep-container
Note: EXAMPLE_NAME
is the example filename minus the directory and .pp
, eg the EXAMPLE_NAME
for examples/foo.pp
would just be foo
.
Example files
- Found inside
/examples
- Regular puppet code
- Will be executed twice (to check for idempotency) with
puppet apply
- MUST contain the magic marker
#@PDQTest
on a line at the top of the file to indicate that this test should be processed by PDQTest - Basically the same as a regular puppet smoke test but run automatically and with system state tests
BATS tests
If you've never seen a BATS test before and have previously been writing server spec code, you'll probably kick youself when you see how simple they are. Here's an example:
# Tests are really easy! just the exit status of running a command...
@test "addition using bc" {
result="$(ls /)"
[ "$?" -eq 0 ]
}
Tests are all written in BASH and the testcase passes if the stanza returns zero. This means that basically any Linux/Unix sysadmin is now empowered to write Testcases and do TDD - score!
Since our tests are written in BASH, there are of course all the usual bash quirks such as failing on incorrect spacing, bizzare variable quoting etc, but given the simplicity gained its a worthwhile tradeoff.
Consider the following (real) test:
@test "ClientAliveCountMax" {
grep "ClientAliveCountMax 1" /etc/ssh/sshd_config
}
Here, we have done exactly what it looks like we have done - checked for the value of a particular setting in a text file. Since our tests our plain old BASH, it means we can do things like test daemons are running with systemctl
, test ports are open with netstat
and do complete system tests wget
and curl
. Cool!
Worked Example
Lets say you have two examples, foo.pp
and init.pp
. In this case, the full range of files to create for acceptance testing would look like this:
mycoolmodule
├── examples
│ ├── foo.pp
│ └── init.pp
├── ...
└── spec
├── acceptance
│ ├── foo__before.bats
│ ├── foo__setup.sh
│ ├── foo.bats
│ ├── init__before.bats
│ ├── init__setup.sh
│ └── init.bats
├── ...
- You can put any puppet code you like (includeing an empty file...) in each of the files under
/examples
and it will executed withpuppet apply
- If you need to test multiple different things (eg different parameters for a new type and provider), you need to create different testcase for each distinct thing to test. See below for help generating these
- You can skip setup or BATS testing before and/or after running
puppet apply
if desired by deleting the appropriate files - If you delete all of the files under
spec/acceptance
for a given example, then PDQTest will just check that puppet runs idempotently for your example - To disable tests temporarily for a specific example, remove the magic marker
#@PDQTest
from the desired example - Nested examples (subdirectories) are not supported at this time
Real world example
See https://github.com/GeoffWilliams/puppet-filemagic for a project with lots of acceptance tests.
Test Generation
Creating a bunch of files manually is an error prone and tedious operation so PDQTest can generate files and boilerplate code for you so that your up and running in the quickest time possible.
Skeleton
pdqtest init
will generate a basic set of tests to get you started (tests init.pp)
RSpec tests
The skeleton tests created by pdqtest init
only cover the init.pp
file which is useful but your likely going to need to support more classes as your modules grow. PDQTest can generate basic RSpec testcases for each new puppet class that exists in the manifests directory for you:
pdqtest generate_rspec
- For every
.pp
file containing a puppet class under/manifests
, RSpec will be generated to:- Check the catalogue compiles
- Check the catalogue contains an instance of the class
- This gives developers an easy place to start writing additional RSpec tests for specific behaviour
- Its safe to run this command whenever you add a new class, it won't overwrite any existing RSpec testcases
Acceptance tests
Generate boilerplate files for each example found (including those without a magic marker)
pdqtest generate_acceptance
Generate boilerplate files for one specific example
pdqtest generate_acceptance examples/mynewthing.pp
- Note: will also create examples/mynewthing.pp if you haven't created it yet
Development
PRs welcome :) Please ensure suitable tests cover any new functionality and that all tests are passing before and after your development work:
bundle exec rake spec
Who should use PDQTest?
You should use pdqtest if you find it increases your productivity and enriches your life
Troubleshooting
- If you can't find the
pdqtest
command and your usingrbenv
be sure to runrbenv rehash
after installing the gem to create the necessary symlinks - Don't forget to run
pdqtest setup
before your firstpdqtest
run to download/update the Docker image - If you need to access private git repositories, make sure to use
fixtures.yml
not.fixtures.yml
- If you need a private key to access private repositories, set this up for your regular git command/ssh and
pdqtest
will reuse the settings - Be sure to annotate the examples you wish to acceptance test with the magic marker comment
#@PDQTest
- Sometimes you might get an error:
Could not resolve the dependencies.
when executing tests. This message is from librarian puppet and usually indicates a conflict between themetadata.json
files somewhere in the set of modules you are attempting to use. Running the commandlibrarian-puppet install --path spec/fixtures/ --destructive --verbose
should give you enough information to resolve the error
Support
This software is not supported by Puppet, Inc. Use at your own risk.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/declarativesystems/pdqtest.
Running tests
- PDQTest includes a comprehensive tests for core library functions. Please ensure tests pass before and after any PRs
- Run all tests
bundle exec rake spec
- Run specific test file
bundle exec rspec ./spec/SPEC/FILE/TO/RUN.rb
- Run specific test case
bundle exec rspec ./spec/SPEC/FILE/TO/RUN.rb:99
(where 99 is the line number of the test)