Unified Collection Testing Strategy - Kick off & Landscape Overview

Yep that’s correct, the files you’ve listed are pretty much the general gist. We run Invoke-PSScriptAnalyzer with a custom ruleset and use pwsh on the ansible-test “controller” host. Our docker images come with all this pre-configured but you can definitely still run it locally if you have the requirements all set up.

I think at a minimum being able to provide your own inventory for test targets is a must have. But the core-ci provided Windows hosts are pretty critical for all the ansible-collections based Windows collections. Without that we will be stuck with either offline CI and manual testing which is not ideal. While I don’t think the tool should hook into the core-ci API and provision these hosts I do think there is a conversation to be had to either expose a specific action in ansible-test or some other tool that uses the core-ci API to provision these hosts for the collections in ansible-collections. The core-ci API is also used to provision the RHEL, FreeBSD, and macOS targets so this functionality would certainly be useful there.

Yep and historically powershell.exe on Windows has been the only workable PowerShell target using Ansible. Ansible 2.21 has added the ability to target pwsh.exe on both Windows and Linux which will help cover a few of those gaps but testing will be very limited for a lot of the collections if you can’t run on Windows as a test target. While I’m certainly a fan of PowerShell based modules I’m sure most people will probably only really care about them when it comes to Windows and Window specific scenarios.

1 Like

No, I was referring to the feature in ansible-test that allows it to request ephemeral cloud resources (virtual machines, cloud credentials) from various cloud providers, including AWS and Azure, without the need for any credentials. This feature is supported for CI jobs on both Azure Pipelines and GitHub Actions. When not run in a CI pipeline on one of those systems, credentials are required.

1 Like

Keep in mind that this only covers what are referred to as unit tests in ansible-test, which are typically a tiny fraction of the tests used for collections, if they exist at all.

The majority of tests tend to be integration tests, which under ansible-test also support code coverage. That means tests written using roles, playbooks and ad-hoc commands are able to collect code coverage for both controller-side plugins and modules, even when run on a different host from the controller. In addition to supporting modules written in Python, coverage is also supported for PowerShell modules. I’m not aware of any tools other than ansible-test that support code coverage in any of these scenarios.

2 Likes

As mentioned in the chat recently, in community.openwrt we are using molecule tests for:

  • generic tests: our extensions/molecule/default scenario does not aim to thoroughly test any particular component, it just cherry picks from a bunch of different ones - it gives us a quick response on whether the collection is working (we have a custom machinery to handle the fact that our modules are in shell script instead of Python)
  • integration tests: per module/plugin, using the same directory structure prescribed by ansible-test integration - we can use it to test, but CI relies on molecule
  • roles tests: despite being placed within a collection, each role (only one right now) may have its own molecule directory, and our CI iterates over those cases and run them separately

Issues

The requirement for the collections to test against devel or milestone comes with a couple of glitches:

  • As consequence of the ansible-core release process: when a new release branch is created off devel, that version is now in a “gap” state. It was happening until a couple of days ago: 2.21 was branched off, but it had not been released yet. The CI workflow did not have that rc1 version in it, nor the b1, b2, b3 before it. In the meanwhile, devel was already numbered as 2.22.
  • ansible-core devel is supporting Python 3.15, which has not been released yet. In community.general (and I suppose most of others) the tests use ansible-test integration, which uses our own custom docker/VM images, for both controller and target. Specifiying the Python version is done in ansible-test’s CLI. In community.openwrt we run molecule in the standard images provided by GHA, and those do not support Python 3.15 yet. I have tried to set the CI to use one of the beta versions, but some dependency used by Sphinx do not support that Python version yet. Therefore, testing devel is incomplete and not directly in my hands.

I reckon these issues are not blockers to anything, we can (probably should) just ignore them as they are solved as soon as releases come out. But I felt it would be important to mention them nonetheless.

Thanks @mattclay - I’ve personally not used this functionality in ansible-test integration for code coverage. @felixfontein do you have an example of this for community.general? I took a look in Azure DevOps but couldn’t find a coverage report.

For community.windows we run with the --coverage option so

ansible-test windows-integration --coverage ...

The final coverage job collects all the coverage data per test matrix, combines it into one then uploads it to codecov.io

Doing it manually is like

# Clear out any existing coverage data
ansible-test coverage erase

# Run test(s) with the coverage collector, can also
# be done with windows-integration, units
ansible-test integration --coverage ping --docker

# View the console report, has a few extra args
ansible-test coverage report

# Combine all the various reports into one for publishing
# to codecov.io or other areas. File is created under
# tests/results/coverage/coverage
ansible-test coverage combine
1 Like

Coverage is only computed during the nightly CI runs. It’s uploaded to Codecov.

1 Like

Collections tested on Azure Pipelines typically run code coverage as part of their scheduled CI runs, uploading to Codecov.

Code coverage isn’t collected and reported for PRs on these collections unless requested (ci_coverage and ci_complete on the last commit message). Reporting on code coverage is really only effective when all tests are run, which is generally limited to schedule runs.

For anyone unfamiliar with this setup, it can give the appearance that code coverage isn’t used at all. Another common misconception is that only unit tests contribute to code coverage, which is something I briefly addressed in a previous comment.

1 Like

This initiative really makes me happy, thank you! I want to follow up on something I believe @kks was touching on earlier.

There are two major groups of Ansible users that are testing collections. Those are:

  1. vendors/communities focused on the python
  2. users/organizations that are focused on roles and playbooks

There are a lot of challenges with the testing for group one that I believe have been summarized already. The problem is even worse for the second group though. The process is not intuitive and there isn’t a good “right” way. The biggest challenges for group two are:

  1. Lack of relevant documentation and examples
  2. Information that is available assumes python knowledge or outdated (prior to collections)
  3. Lack of emphasis on molecule-plugins that provide an easy-button for testing
  4. Multiple layers of abstraction (/extensions/molecule/utils + /extensions/molecule/role + /tests/integration/X)
  5. Layers of helper tools that each have their own learning curve

I’d ask that we not lose sight of the second group of users and their needs in this effort. Over the years it feels like the testing tools have moved away from some of the principals laid out in The Zen of Ansible".

  • Ansible is not Python.
  • YAML sucks for coding.
  • Playbooks are not for programming.
  • Ansible users are (most likely) not programmers.
4 Likes

This is a very good point, indeed.

Hi @Jeff_Pullen - Thanks for your feedback here. Wanted to ask some follow up questions.

With group two as you explained it, are you referring to somebody testing Ansible content (such as a role or playbook) outside of a collection structure? E.g., a standalone role in a GitHub repo?

Lack of documentation and examples for what exactly? Do you have specific tooling or scenarios in mind here?

Is there a particular guide on docs.ansible.com that you’re referring to here?

I was discussing with @konstruktoid about molecule-plugins recently on Matrix. We were talking about how the docker driver with newer molecule versions doesn’t need to be installed anymore. The community.docker collection can be used to create a container for the scenario in the create sequence. See the example here which parses ansible-collection-hardening/extensions/molecule/delete_users_docker/molecule.yml at molecule-docker-scenario · dbrennand/ansible-collection-hardening · GitHub to create the containers.

This is what this point from the original post is alluding to:

1 Like

@dbrennand I appreciate the follow up. Lots of great questions, but these are going to get confusing smashing it all together. I’ll try my best.

Question 1:

With group two as you explained it, are you referring to somebody testing Ansible content (such as a role or playbook) outside of a collection structure? E.g., a standalone role in a GitHub repo?

I’m focused on testing ansible content as part of a collection. The process for testing stand-alone roles has extensive documentation and examples and the process has generally stayed the same for a very long time.

Question 2:

Lack of documentation and examples for what exactly? Do you have specific tooling or scenarios in mind here?

How to test ansible content (YAML) as part of a collection.

The most typical scenario that has to be out-of-the-box is running local podman/docker containers that include systemd for an automation developer to test as they’re writing the code and validating ansible-lint won’t fail a pipeline.

Question 3:

Information that is available assumes python knowledge or outdated (prior to collections)

The challenge is mostly that there is nearly a decade of content covering how to do this (books, videos, blogs, etc) that sort-of work, don’t work at all anymore, or take the person down some really frustrating paths.

I just reviewed the molecule documentation and see there have been some major reworks in the past year that I wasn’t tracking. This is a huge step in the right direction thanks to folks like @cidrblock . There is now enough information there for someone to get started, which is a big improvement. Assuming people are aware, we likely see more examples of it that can be used as a reference.

So much of the the information related to collection testing assumes that it is module focused. We don’t do a great job of differentiating role only collections from collections with modules. So users will encounter a long list of tools that seem required but may not be needed for their use-case.

Question / Comment 4:

I was discussing with @konstruktoid about molecule-plugins recently on Matrix. We were talking about how the docker driver with newer molecule versions doesn’t need to be installed anymore. The community.docker collection can be used to create a container for the scenario in the create sequence. See the example here which parses ansible-collection-hardening/extensions/molecule/delete_users_docker/molecule.yml at molecule-docker-scenario · dbrennand/ansible-collection-hardening · GitHub to create the containers.

This is my major point of contention with the direction of ansible content testing. It appears that flexibility is being prioritized over simplicity. The ‘ansible-native’ approach provides a tremendous amount of capability but adds a lot of complexity. Enough complexity that even having done it many times I still end up having to relearn and troubleshoot it every time to get things working (for example fighting all the relative paths in the molecule configs). It also takes a non-opinionated stance on the implementation so there are many ways to get it working that have the same effect but are very unique. This makes troubleshooting and maintenance more difficult.

The old way:

# molecule.yml
driver:
  name: docker

The new Ansible Native alternative:

# create.yml
---
- name: Create
  hosts: localhost
  gather_facts: false
  vars:
    molecule_inventory:
      all:
        children:
          molecule:
            hosts: {}
  tasks:
    - name: Create containers
      community.docker.docker_container:
        name: "{{ item.name }}"
        hostname: "{{ item.name }}"
        image: "{{ item.image }}"
        state: started
        command: "{{ item.command | default('sleep 1d') }}"
        privileged: "{{ item.privileged | default(false) }}"
        volumes: "{{ item.volumes | default(omit) }}"
        log_driver: json-file
      register: result
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Print container details
      ansible.builtin.debug:
        msg: "{{ result.results }}"

    - name: Fail if container is not running
      when: >
        item.container.State.ExitCode != 0 or not item.container.State.Running
      ansible.builtin.fail:
        msg: >-
          Container {{ item.container.Name }} failed to start properly.
          Exit Code: {{ item.container.State.ExitCode }}.
          Running: {{ item.container.State.Running }}.
      loop: "{{ result.results }}"
      loop_control:
        label: "{{ item.container.Name }}"

    - name: Add containers to molecule inventory
      vars:
        inventory_partial_yaml: |
          all:
            children:
              molecule:
                hosts:
                  "{{ item.name }}":
                    ansible_connection: community.docker.docker
                    ansible_python_interpreter: /usr/bin/python3
      ansible.builtin.set_fact:
        molecule_inventory: >-
          {{ molecule_inventory | combine(inventory_partial_yaml | from_yaml,
          recursive=true) }}
      loop: "{{ molecule_yml.platforms }}"
      loop_control:
        label: "{{ item.name }}"

    - name: Write molecule inventory
      ansible.builtin.copy:
        content: "{{ molecule_inventory | to_yaml }}"
        dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml"
        mode: "0600"

    - name: Force inventory refresh
      ansible.builtin.meta: refresh_inventory

    - name: Fail if molecule group is missing
      ansible.builtin.assert:
        that: "'molecule' in groups"
        fail_msg: "molecule group was not found inside inventory groups: {{ groups }}"
      run_once: true

- name: Validate inventory
  hosts: molecule
  gather_facts: false
  tasks:
    - name: Check container access
      ansible.builtin.raw: /bin/true
      changed_when: false

There may be valid reasons to move away from molecule drivers, but I think its worth considering how we can still live up to the goal of “radically simple IT automation”.

3 Likes

Hi @Jeff_Pullen

Thank you for your detailed answers :slightly_smiling_face:

From my own experience, molecule is well suited to this scenario. I was reviewing the molecule documentation and there is Systemd container - Ansible Molecule. Do you think this guide would benefit from being a full user guide? For example, showing the create.yml playbook example and the configuration of the container under platforms in molecule.yml to support systemd inside a container using docker or podman as the container engine?

Thank you for your feedback here. I think there is an opportunity here for us to add documentation in the future specifically around role testing. Right now, when you navigate to Developers | Ansible documentation | Ansible documentation there is no guide about role testing.

Another short update that Bug: Molecule package missing from Tox Integration Environment install_deps · Issue #549 · ansible/tox-ansible · GitHub is now closed. :slightly_smiling_face: