Tests

Enabling

Tests may be written in different ways, but are exposed and invoked in a standard way as defined by the Standard Test Interface directly in the package git repository. It is also possible to enable pipeline for the tests namespace, see Testing Tests for details. To start working on tests you can clone a package repo directly:

git clone https://src.fedoraproject.org/rpms/qrencode.git

You can also use the fedpkg to clone the repo. See the Package Maintenance Guide for more info about the tool:

fedpkg clone -a qrencode

Tests are enabled by including the tests.yml file under the tests directory:

cd qrencode/tests
cat tests.yml

Tests are wrapped or written as Ansible playbooks. Here is an example of a simple playbok which enables a single smoke test of the qrencode package:

- hosts: localhost
  roles:
  - role: standard-test-beakerlib
    tags:
    - classic
    - container
    - atomic
    tests:
    - smoke
    required_packages:
    - qrencode
    - file

Let’s now briefly look at the playbook to see which variables are defined in order to enable the smoke test:

role

this test uses role standard-test-beakerlib from Standard Test Roles to run a BeakerLib test

tags

all three test subjects (classic rpm, docker container and atomic host) are relevant for this test

tests

list of tests to be executed (here we have just a single smoke test)

required_packages

list of rpm packages required for test execution

It is possible to separate tests into multiple playbooks, each of them can represent a test or a part of a test. Testing system will run each playbook matching the glob tests/tests*.yml separately in a clean environment. Optionally you can have multiple playbooks without the tests prefix and link them from the tests.yml file. Let’s have a look at the gzip example:

> fedpkg clone -a gzip
Cloning into 'gzip'...
> cd gzip/tests/
> ls
test-simple  test_simple.yml  tests.yml
> cat tests.yml
- include: test_simple.yml

Executing

Before running tests make sure you have the following dependencies installed on your system:

dnf install ansible python2-dnf libselinux-python standard-test-roles

Although some playbooks may function without sudo, tests are always invoked as root. The test itself may set up users and/or drop permissions if a part of that test. But in general be sure to be root when invoking tests.

Tests may modify or destroy your environment
It’s recommended to use a virtual machine for testing to prevent any unwated changes performed by the test to your system.

Running a test directly on the current system is easy:

ansible-playbook tests.yml

To only run tests that are suited for classic systems installed by yum or dnf use the --tags argument:

ansible-playbook --tags=classic tests.yml

See Standard Test Roles documentation for detailed instructions how to run tests for a specific Rpm Package, Docker Container or Atomic Host.

Writing

Test code itself can be stored directly in the dist-git (recommended as default) or fetched from another repository hosted in the Fedora infrastructure such as the Test Namespace. The simplest way to add a new test is by using one of the existing Standard Test Roles which take care of many implementatin details. If you want to create a custom test follow instructions below.

Once you’ve identified a dist-git repository you will be adding new tests to (above), you can start to write a new Ansible test. Create an Ansible playbook with a new name. Make sure the extension is .yml. Lets place the following example in test_pid_1.yml file.

---
- hosts: localhost
  vars:
  - artifacts: "{{ lookup('env', 'TEST_ARTIFACTS')|default('./artifacts', true) }}"
  tags:
  - atomic
  - classic
  - container
  tasks:
  - name: Test block
    block:
      - name: Test that /proc/1 exists
        shell: |
            ls /proc > /tmp/test.log || exit 1
            grep -qw 1 /tmp/test.log && result=pass || result=fail
            echo -e "results:\n- {result: $result, test: proc}" > /tmp/results.yml

    always:
      - name: Pull out the artifacts
        fetch:
          dest: "{{ artifacts }}/"
          src: "{{ item }}"
          flat: yes
        with_items:
          - /tmp/test.log
          - /tmp/results.yml

All tests have an artifacts directory where they place their output. The testing or CI system that invokes the test will fill in this variable with a directory that it will archive. We ensure this directory exists in the test.

By use of tags we note what kind of systems this test is suitable to run on. When including additional tasks such as pre_tasks make sure you set appropriate tag as well. In addition to tags listed above it’s also possible to use always to denote the task should run for all environments. For example:

- hosts: localhost
  pre_tasks:
  - name: Set up a test user
    tags: always
    user:
      name: test
      groups:
        - wheel
        - adm

The block is the section that runs the actual test. In this example, we use a rather convoluted way of checking that PID 1 exists. However, by doing so, we place an extra test artifact in the artifacts directory.

Lastly, we download the artifacts. Remember that the test is not always running on the same system that it was invoked on. Try running this example test against an Atomic Host or Docker Container. It should pass. Try changing the /proc/1 argument to another value, and the test should fail.

You can use most of the Ansible techniques in your playbooks. Take a look at the Standard Test Roles for Ansible roles to make writing your tests easier.

Marking the test to be run

Just having a .yml file in the right directory doesn’t yet mean it will be invoked. Make sure to reference or add it from a tests.yml playbook. This is the entry point that the testing or CI system will use to invoke all the tests for a given package.

If the tests.yml file doesn’t yet exist, create it. Lets continue with our above example and create a tests.yml with the following content:

- import_playbook: test_pid_1.yml

You can now run this test with the standard commands above.

See the Quick Start Guide to get recommendations for contributing new tests.

Wrapping

Let’s say you have a script that runs a test. Its stdout and stderr is the test output, and an exit status of zero indicates success. Here’s how we would wrap that test to be invoked. Lets say we have a simple script like in a file called test-simple

#!/bin/sh
set -ex
# exercise installed gzip/gunzip programs
echo "Bla" > bla.file
cp bla.file bla.file.orig
gzip bla.file
gunzip bla.file.gz
cmp bla.file bla.file.orig
rm bla.file bla.file.orig

We can write an Ansible wrapper for this script like this in test_simple.yml:

---
- hosts: localhost
  vars:
  - artifacts: "{{ lookup('env', 'TEST_ARTIFACTS')|default('./artifacts', true) }}"
  tags:
  - atomic
  - classic
  - container
  remote_user: root
  tasks:
  - name: Install the test files
    copy: src={{ item.file }} dest=/usr/local/bin/{{ item.dest }} mode=0755
    with_items:
    - {file: test-simple, dest: test-simple }

  - name: Test block
    block:
      - name: Execute the tests
        shell: |
          /usr/local/bin/test-simple &> /tmp/test.log && result=pass || result=fail
          echo -e "results:\n- {result: $result, test: simple}" > /tmp/results.yml

    always:
      - name: Pull out the logs
        fetch:
          dest: "{{ artifacts }}/"
          src: "{{ item }}"
          flat: yes
        with_items:
          - /tmp/test.log
          - /tmp/results.yml

All tests have an artifacts directory where they place their output. The testing or CI system that invokes the test will fill in this variable with a directory that it will archive. We create ensure this directory exists in the test.

The block is the section that runs the actual test.

Lastly, we download the artifacts. Remember that the test is not always running on the same system that it was invoked on.

If the tests.yml file doesn’t yet exist, create it. Lets continue with our above example and create a tests.yml with the following content:

- import_playbook: test_simple.yml

Try running this example test against an Atomic Host or Docker Container. It should pass.

See Standard Test Roles documentation for instructions how to wrap a BeakerLib and RHTS tests.

See the Quick Start Guide to get recommendations for contributing new tests.

Preparation

If you need to do any adjustments to the system before testing, include an extra ansible task before the testing section. For example this will upgrade all packages on the system to the latest version:

- hosts: localhost
  tags:
    - classic
  tasks:
    - dnf:
        name: "*"
        state: latest

- hosts: localhost
  roles:
  - role: standard-test-basic
    tags:
    - classic
    tests:
    - smoke38:
        dir: smoke
        run: VERSION=3.8 METHOD=virtualenv ./venv.sh