pytest-ansible


Namepytest-ansible JSON
Version 24.1.2 PyPI version JSON
download
home_page
SummaryPlugin for pytest to simplify calling ansible modules from tests or fixtures
upload_time2024-01-18 11:38:07
maintainer
docs_urlNone
author
requires_python>=3.10
licenseMIT
keywords ansible testing pytest
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pytest-ansible

[![Build Status](https://github.com/ansible-community/pytest-ansible/actions/workflows/tox.yml/badge.svg)](https://github.com/ansible-community/pytest-ansible/actions/workflows/tox.yml)
[![Version](https://img.shields.io/pypi/v/pytest-ansible.svg)](https://pypi.python.org/pypi/pytest-ansible/)
[![License](https://img.shields.io/pypi/l/pytest-ansible.svg)](https://pypi.python.org/pypi/pytest-ansible/)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/pytest-ansible.svg)](https://pypi.python.org/pypi/pytest-ansible/)

The `pytest-ansible` plugin is designed to provide seamless integration between
`pytest` and `Ansible`, allowing you to efficiently run and test Ansible-related
tasks and scenarios within your pytest test suite. This plugin enhances the
testing workflow by offering three distinct pieces of functionality:

1. **Unit Testing for Ansible Collections**: This feature aids in running unit
   tests for `Ansible collections` using `pytest`. It allows you to validate the
   behavior of your Ansible `modules` and `roles` in isolation, ensuring that
   each component functions as expected.

2. **Molecule Scenario Integration**: The plugin assists in running Molecule
   `scenarios` using `pytest`. This integration streamlines the testing of
   Ansible roles and playbooks across different environments, making it easier
   to identify and fix issues across diverse setups.

3. **Ansible Integration for Pytest Tests**: With this functionality, you can
   seamlessly use `Ansible` from within your `pytest` tests. This opens up
   possibilities to interact with Ansible components and perform tasks like
   provisioning resources, testing configurations, and more, all while
   leveraging the power and flexibility of pytest.

## Supported Ansible

Pytest Ansible will only support versions of **python** and **ansible-core**
which are under
[active upstream support](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#release-schedule)
which currently translates to:

- Python 3.10 or newer
- Ansible-core 2.14 or newer

## Installation

Install this plugin using `pip`:

```
pip install pytest-ansible
```

## Getting Started

### Unit Testing for Ansible Collections

The `pytest-ansible-units` plugin allows ansible collection's unit tests to be
run with only `pytest`. It offers a focused approach to testing individual
Ansible modules. With this plugin, you can write and execute unit tests
specifically for Ansible modules, ensuring the accuracy and reliability of your
module code. This is particularly useful for verifying the correctness of module
behavior in isolation.

To use `pytest-ansible-units`, follow these steps:

1. Install the plugin using pip:

```
pip install pytest-ansible
```

2. Ensure you have Python 3.10 or greater, ansible-core, and pyyaml installed.

3. Depending on your preferred directory structure, you can clone collections
   into the appropriate paths.

   - **Collection Tree Approach**: The preferred approach is to clone the
     collections being developed into it's proper collection tree path. This
     eliminates the need for any symlinks and other collections being developed
     can be cloned into the same tree structure.

     ```
     git clone <repo> collections/ansible_collections/<namespace>/<name>
     ```

     Note: Run `pytest` in the root of the collection directory, adjacent to the
     collection's `galaxy.yml` file.

   - **Shallow Tree Approach**:

     ```
     git clone <repo>
     ```

     Notes:

     - Run `pytest` in the root of the collection directory, adjacent to the
       collection's `galaxy.yml` file.
     - A collections directory will be created in the repository directory, and
       collection content will be linked into it.

4. Execute the unit tests using pytest: `pytest tests`

### Help

The following may be added to the collections' `pyproject.toml` file to limit
warnings and set the default path for the collection's tests

```
[tool.pytest.ini_options]
testpaths = [
    "tests",
]
filterwarnings = [
    'ignore:AnsibleCollectionFinder has already been configured',
]
```

Information from the `galaxy.yml` file is used to build the `collections`
directory structure and link the contents. The `galaxy.yml` file should reflect
the correct collection namespace and name.

One way to detect issues without running the tests is to run:

```
pytest --collect-only
```

The follow errors may be seen:

```
E   ModuleNotFoundError: No module named 'ansible_collections'
```

- Check the `galaxy.yml` file for an accurate namespace and name
- Ensure `pytest` is being run from the collection's root directory, adjacent to
  the `galaxy.yml`

```
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules
```

## Molecule Scenario Integration

This functionality assists in running Molecule `scenarios` using `pytest`. It
enables pytest discovery of all `molecule.yml` files inside the codebase and
runs them as pytest tests. It allows you to include Molecule scenarios as part
of your pytest test suite, allowing you to thoroughly test your Ansible roles
and playbooks across different scenarios and environments.

## Running molecule scenarios using pytest

Molecule scenarios can be tested using 2 different methods if molecule is
installed.

**Recommended:**

Add a `test_integration.py` file to the `tests/integration` directory of the
ansible collection:

```
"""Tests for molecule scenarios."""
from __future__ import absolute_import, division, print_function

from pytest_ansible.molecule import MoleculeScenario


def test_integration(molecule_scenario: MoleculeScenario) -> None:
    """Run molecule for each scenario.

    :param molecule_scenario: The molecule scenario object
    """
    proc = molecule_scenario.test()
    assert proc.returncode == 0
```

The `molecule_scenario` fixture provides parameterized molecule scenarios
discovered in the collection's `extensions/molecule` directory, as well as other
directories within the collection.

`molecule test -s <scenario>` will be run for each scenario and a completed
subprocess returned from the `test()` call.

**Legacy:**

Run molecule with the `--molecule` command line parameter to inject each
molecule directory found in the current working directory. Each scenario will be
injected as an external test in the the tests available for pytest. Due to the
nature of this approach, the molecule scenarios are not represented as python
tests and may not show in the IDE's pytest test tree.

To run Molecule scenarios using pytest, follow these steps:

1. Install the `pytest-ansible` plugin using pip:

```
pip install pytest-ansible
```

2. Execute pytest to run Molecule scenarios: `pytest`

## Ansible Integration for Pytest Tests

The `ansible_module`, `ansible_adhoc`, `localhost`, and `ansible_facts` fixtures
are provided to help you integrate Ansible functionalities into your pytest
tests. These fixtures allow you to interact with Ansible modules, run commands
on localhost, fetch Ansible facts, and more.

## Fixtures and helpers for use in tests

Here's a quick overview of the available fixtures:

- `ansible_module`: Allows you to call Ansible modules directly within your test
  functions.
- `ansible_adhoc`: Provides a function to initialize a `HostManager` object to
  work with Ansible inventory.
- `localhost`: A convenience fixture for running Ansible modules that typically
  run on the local machine.
- `ansible_facts`: Returns a JSON structure representing system facts for the
  associated inventory.

### Usage

Once installed, the following `pytest` command-line parameters are available:

```bash
pytest \
    [--inventory <path_to_inventory>] \
    [--extra-inventory <path_to_extra_inventory>] \
    [--host-pattern <host-pattern>] \
    [--connection <plugin>] \
    [--module-path <path_to_modules] \
    [--user <username>] \
    [--become] \
    [--become-user <username>] \
    [--become-method <method>] \
    [--ask-become-pass] \
    [--limit <limit>] \
    [--ansible-unit-inject-only] \
    [--molecule] \
    [--molecule-unavailable-driver] \
    [--skip-no-git-change] \
    [--check]
```

### Inventory

Using ansible first starts with defining your inventory. This can be done in
several ways, but to start, we'll use the `ansible_adhoc` fixture.

```python
def test_my_inventory(ansible_adhoc):
    hosts = ansible_adhoc()
```

In the example above, the `hosts` variable is an instance of the `HostManager`
class and describes your ansible inventory. For this to work, you'll need to
tell `ansible` where to find your inventory. Inventory can be anything supported
by ansible, which includes an
[INI file](http://docs.ansible.com/ansible/latest/intro_inventory.html) or an
executable script that returns
[properly formatted JSON](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html).
For example,

```bash
pytest --inventory my_inventory.ini --host-pattern all
```

or

```bash
pytest --inventory path/to/my/script.py --host-pattern webservers
```

or

```bash
pytest --inventory one.example.com,two.example.com --host-pattern all
```

In the above examples, the inventory provided at runtime will be used in all
tests that use the `ansible_adhoc` fixture. A more realistic scenario may
involve using different inventory files (or host patterns) with different tests.
To accomplish this, the fixture `ansible_adhoc` allows you to customize the
inventory parameters. Read on for more detail on using the `ansible_adhoc`
fixture.

### Extra Inventory

Using ansible first starts with defining your extra inventory. This feature was
added in version 2.3.0, and is intended to allow the user to work with two
different inventories. This can be done in several ways, but to start, we'll use
the `ansible_adhoc` fixture.

For example,

```bash
pytest --inventory my_inventory.ini --extra-inventory my_second_inventory.ini --host-pattern host_in_second_inventory
```

#### Fixture `ansible_adhoc`

The `ansible_adhoc` fixture returns a function used to initialize a
`HostManager` object. The `ansible_adhoc` fixture will default to parameters
supplied to the `pytest` command-line, but also allows one to provide keyword
arguments used to initialize the inventory.

The example below demonstrates basic usage with options supplied at run-time to
`pytest`.

```python
def test_all_the_pings(ansible_adhoc):
    ansible_adhoc().all.ping()
```

The following example demonstrates available keyword arguments when creating a
`HostManager` object.

```python
def test_uptime(ansible_adhoc):
    # take down the database
    ansible_adhoc(inventory='db1.example.com,', user='ec2-user',
        become=True, become_user='root').all.command('reboot')
```

The `HostManager` object returned by the `ansible_adhoc()` function provides
numerous ways of calling ansible modules against some, or all, of the inventory.
The following demonstrates sample usage.

```python
def test_host_manager(ansible_adhoc):
    hosts = ansible_adhoc()

    # __getitem__
    hosts['all'].ping()
    hosts['localhost'].ping()

    # __getattr__
    hosts.all.ping()
    hosts.localhost.ping()

    # Supports [ansible host patterns](http://docs.ansible.com/ansible/latest/intro_patterns.html)
    hosts['webservers:!phoenix'].ping()  # all webservers that are not in phoenix
    hosts[0].ping()
    hosts[0:2].ping()

    assert 'one.example.com' in hosts

    assert hasattr(hosts, 'two.example.com')

    for a_host in hosts:
        a_host.ping()
```

#### Fixture `localhost`

The `localhost` fixture is a convenience fixture that surfaces a
`ModuleDispatcher` instance for ansible host running `pytest`. This is
convenient when using ansible modules that typically run on the local machine,
such as cloud modules (ec2, gce etc...).

```python
def test_do_something_cloudy(localhost, ansible_adhoc):
    """Deploy an ec2 instance using multiple fixtures."""
    params = dict(
        key_name='some_key',
        instance_type='t2.micro',
        image='ami-123456',
        wait=True,
        group='webservers',
        count=1,
        vpc_subnet_id='subnet-29e63245',
        assign_public_ip=True,
    )

    # Deploy an ec2 instance from localhost using the `ansible_adhoc` fixture
    ansible_adhoc(inventory='localhost,', connection='local').localhost.ec2(**params)

    # Deploy an ec2 instance from localhost using the `localhost` fixture
    localhost.ec2(**params)
```

#### Fixture `ansible_module`

The `ansible_module` fixture allows tests and fixtures to call
[ansible modules](http://docs.ansible.com/modules.html). Unlike the
`ansible_adhoc` fixture, this fixture only uses the options supplied to `pytest`
at run time.

A very basic example demonstrating the ansible
[`ping` module](http://docs.ansible.com/ping_module.html):

```python
def test_ping(ansible_module):
    ansible_module.ping()
```

A more involved example of updating the sshd configuration, and restarting the
service.

```python
def test_sshd_config(ansible_module):

    # update sshd MaxSessions
    contacted = ansible_module.lineinfile(
        dest="/etc/ssh/sshd_config",
        regexp="^#?MaxSessions .*",
        line="MaxSessions 150")
    )

    # assert desired outcome
    for (host, result) in contacted.items():
        assert 'failed' not in result, result['msg']
        assert 'changed' in result

    # restart sshd
    contacted = ansible_module.service(
        name="sshd",
        state="restarted"
    )

    # assert successful restart
    for (host, result) in contacted.items():
        assert 'changed' in result and result['changed']
        assert result['name'] == 'sshd'

    # do other stuff ...
```

#### Fixture `ansible_facts`

The `ansible_facts` fixture returns a JSON structure representing the system
facts for the associated inventory. Sample fact data is available in the
[ansible documentation](http://docs.ansible.com/playbooks_variables.html#information-discovered-from-systems-facts).

Note, this fixture is provided for convenience and could easily be called using
`ansible_module.setup()`.

A systems facts can be useful when deciding whether to skip a test ...

```python
def test_something_with_amazon_ec2(ansible_facts):
    for facts in ansible_facts:
        if 'ec2.internal' != facts['ansible_domain']:
            pytest.skip("This test only applies to ec2 instances")

```

Additionally, since facts are just ansible modules, you could inspect the
contents of the `ec2_facts` module for greater granularity ...

```python

def test_terminate_us_east_1_instances(ansible_adhoc):

    for facts in ansible_adhoc().all.ec2_facts():
        if facts['ansible_ec2_placement_region'].startswith('us-east'):
            '''do some testing'''
```

#### Parameterize with `pytest.mark.ansible`

Perhaps the `--ansible-inventory=<inventory>` includes many systems, but you
only wish to interact with a subset. The `pytest.mark.ansible` marker can be
used to modify the `pytest-ansible` command-line parameters for a single test.
Please note, the fixture `ansible_adhoc` is the prefer mechanism for interacting
with ansible inventory within tests.

For example, to interact with the local system, you would adjust the
`host_pattern` and `connection` parameters.

```python
@pytest.mark.ansible(host_pattern='local,', connection='local')
def test_copy_local(ansible_module):

    # create a file with random data
    contacted = ansible_module.copy(
        dest='/etc/motd',
        content='PyTest is amazing!',
        owner='root',
        group='root',
        mode='0644',
    )

    # assert only a single host was contacted
    assert len(contacted) == 1, \
        "Unexpected number of hosts contacted (%d != %d)" % \
        (1, len(contacted))

    assert 'local' in contacted

    # assert the copy module reported changes
    assert 'changed' in contacted['local']
    assert contacted['local']['changed']
```

Note, the parameters provided by `pytest.mark.ansible` will apply to all class
methods.

```python
@pytest.mark.ansible(host_pattern='local,', connection='local')
class Test_Local(object):
    def test_install(self, ansible_module):
        '''do some testing'''
    def test_template(self, ansible_module):
        '''do some testing'''
    def test_service(self, ansible_module):
        '''do some testing'''
```

#### Inspecting results

When using the `ansible_adhoc`, `localhost` or `ansible_module` fixtures, the
object returned will be an instance of class `AdHocResult`. The `AdHocResult`
class can be inspected as follows:

```python

def test_adhoc_result(ansible_adhoc):
    contacted = ansible_adhoc(inventory=my_inventory).command("date")

    # As a dictionary
    for (host, result) in contacted.items():
        assert result.is_successful, "Failed on host %s" % host
    for result in contacted.values():
        assert result.is_successful
    for host in contacted.keys():
        assert host in ['localhost', 'one.example.com']

    assert contacted.localhost.is_successful

    # As a list
    assert len(contacted) > 0
    assert 'localhost' in contacted

    # As an iterator
    for result in contacted:
        assert result.is_successful

    # With __getattr__
    assert contacted.localhost.is_successful

    # Or __getitem__
    assert contacted['localhost'].is_successful
```

Using the `AdHocResult` object provides ways to conveniently access results for
different hosts involved in the ansible adhoc command. Once the specific host
result is found, you may inspect the result of the ansible adhoc command on that
use by way of the `ModuleResult` interface. The `ModuleResult` class represents
the dictionary returned by the ansible module for a particular host. The
contents of the dictionary depend on the module called.

The `ModuleResult` interface provides some convenient properties to determine
the success of the module call. Examples are included below.

```python

def test_module_result(localhost):
    contacted = localhost.command("find /tmp")

    assert contacted.localhost.is_successful
    assert contacted.localhost.is_ok
    assert contacted.localhost.is_changed
    assert not contacted.localhost.is_failed

    contacted = localhost.shell("exit 1")
    assert contacted.localhost.is_failed
    assert not contacted.localhost.is_successful
```

The contents of the JSON returned by an ansible module differs from module to
module. For guidance, consult the documentation and examples for the specific
[ansible module](http://docs.ansible.com/modules_by_category.html).

#### Exception handling

If `ansible` is unable to connect to any inventory, an exception will be raised.

```python
@pytest.mark.ansible(inventory='unreachable.example.com,')
def test_shutdown(ansible_module):

    # attempt to ping a host that is down (or doesn't exist)
    pytest.raises(pytest_ansible.AnsibleHostUnreachable):
        ansible_module.ping()
```

Sometimes, only a single host is unreachable, and others will have properly
returned data. The following demonstrates how to catch the exception, and
inspect the results.

```python
@pytest.mark.ansible(inventory='good:bad')
def test_inventory_unreachable(ansible_module):
    exc_info = pytest.raises(pytest_ansible.AnsibleHostUnreachable, ansible_module.ping)
    (contacted, dark) = exc_info.value.results

    # inspect the JSON result...
    for (host, result) in contacted.items():
        assert result['ping'] == 'pong'

    for (host, result) in dark.items():
        assert result['failed'] == True
```

## Contributing

Contributions are very welcome. Tests can be run with
[tox](https://tox.wiki/en/latest/), please ensure the coverage at least stays
the same before you submit a pull request.

## License

Distributed under the terms of the [MIT](https://opensource.org/license/mit/)
license, "pytest-ansible" is free and open source software

## Issues

If you encounter any problems, please
[file an issue](https://github.com/pycontribs/pytest-ansible/issues) along with
a detailed description.

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "pytest-ansible",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "Ansible by Red Hat <info@ansible.com>",
    "keywords": "ansible,testing,pytest",
    "author": "",
    "author_email": "Ansible by Red Hat <info@ansible.com>",
    "download_url": "https://files.pythonhosted.org/packages/e1/b9/b0adb889c629ca37aef8629c480914ac25db89ca753da7ca30d3d24df096/pytest-ansible-24.1.2.tar.gz",
    "platform": null,
    "description": "# pytest-ansible\n\n[![Build Status](https://github.com/ansible-community/pytest-ansible/actions/workflows/tox.yml/badge.svg)](https://github.com/ansible-community/pytest-ansible/actions/workflows/tox.yml)\n[![Version](https://img.shields.io/pypi/v/pytest-ansible.svg)](https://pypi.python.org/pypi/pytest-ansible/)\n[![License](https://img.shields.io/pypi/l/pytest-ansible.svg)](https://pypi.python.org/pypi/pytest-ansible/)\n[![Supported Python Versions](https://img.shields.io/pypi/pyversions/pytest-ansible.svg)](https://pypi.python.org/pypi/pytest-ansible/)\n\nThe `pytest-ansible` plugin is designed to provide seamless integration between\n`pytest` and `Ansible`, allowing you to efficiently run and test Ansible-related\ntasks and scenarios within your pytest test suite. This plugin enhances the\ntesting workflow by offering three distinct pieces of functionality:\n\n1. **Unit Testing for Ansible Collections**: This feature aids in running unit\n   tests for `Ansible collections` using `pytest`. It allows you to validate the\n   behavior of your Ansible `modules` and `roles` in isolation, ensuring that\n   each component functions as expected.\n\n2. **Molecule Scenario Integration**: The plugin assists in running Molecule\n   `scenarios` using `pytest`. This integration streamlines the testing of\n   Ansible roles and playbooks across different environments, making it easier\n   to identify and fix issues across diverse setups.\n\n3. **Ansible Integration for Pytest Tests**: With this functionality, you can\n   seamlessly use `Ansible` from within your `pytest` tests. This opens up\n   possibilities to interact with Ansible components and perform tasks like\n   provisioning resources, testing configurations, and more, all while\n   leveraging the power and flexibility of pytest.\n\n## Supported Ansible\n\nPytest Ansible will only support versions of **python** and **ansible-core**\nwhich are under\n[active upstream support](https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#release-schedule)\nwhich currently translates to:\n\n- Python 3.10 or newer\n- Ansible-core 2.14 or newer\n\n## Installation\n\nInstall this plugin using `pip`:\n\n```\npip install pytest-ansible\n```\n\n## Getting Started\n\n### Unit Testing for Ansible Collections\n\nThe `pytest-ansible-units` plugin allows ansible collection's unit tests to be\nrun with only `pytest`. It offers a focused approach to testing individual\nAnsible modules. With this plugin, you can write and execute unit tests\nspecifically for Ansible modules, ensuring the accuracy and reliability of your\nmodule code. This is particularly useful for verifying the correctness of module\nbehavior in isolation.\n\nTo use `pytest-ansible-units`, follow these steps:\n\n1. Install the plugin using pip:\n\n```\npip install pytest-ansible\n```\n\n2. Ensure you have Python 3.10 or greater, ansible-core, and pyyaml installed.\n\n3. Depending on your preferred directory structure, you can clone collections\n   into the appropriate paths.\n\n   - **Collection Tree Approach**: The preferred approach is to clone the\n     collections being developed into it's proper collection tree path. This\n     eliminates the need for any symlinks and other collections being developed\n     can be cloned into the same tree structure.\n\n     ```\n     git clone <repo> collections/ansible_collections/<namespace>/<name>\n     ```\n\n     Note: Run `pytest` in the root of the collection directory, adjacent to the\n     collection's `galaxy.yml` file.\n\n   - **Shallow Tree Approach**:\n\n     ```\n     git clone <repo>\n     ```\n\n     Notes:\n\n     - Run `pytest` in the root of the collection directory, adjacent to the\n       collection's `galaxy.yml` file.\n     - A collections directory will be created in the repository directory, and\n       collection content will be linked into it.\n\n4. Execute the unit tests using pytest: `pytest tests`\n\n### Help\n\nThe following may be added to the collections' `pyproject.toml` file to limit\nwarnings and set the default path for the collection's tests\n\n```\n[tool.pytest.ini_options]\ntestpaths = [\n    \"tests\",\n]\nfilterwarnings = [\n    'ignore:AnsibleCollectionFinder has already been configured',\n]\n```\n\nInformation from the `galaxy.yml` file is used to build the `collections`\ndirectory structure and link the contents. The `galaxy.yml` file should reflect\nthe correct collection namespace and name.\n\nOne way to detect issues without running the tests is to run:\n\n```\npytest --collect-only\n```\n\nThe follow errors may be seen:\n\n```\nE   ModuleNotFoundError: No module named 'ansible_collections'\n```\n\n- Check the `galaxy.yml` file for an accurate namespace and name\n- Ensure `pytest` is being run from the collection's root directory, adjacent to\n  the `galaxy.yml`\n\n```\nHINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules\n```\n\n## Molecule Scenario Integration\n\nThis functionality assists in running Molecule `scenarios` using `pytest`. It\nenables pytest discovery of all `molecule.yml` files inside the codebase and\nruns them as pytest tests. It allows you to include Molecule scenarios as part\nof your pytest test suite, allowing you to thoroughly test your Ansible roles\nand playbooks across different scenarios and environments.\n\n## Running molecule scenarios using pytest\n\nMolecule scenarios can be tested using 2 different methods if molecule is\ninstalled.\n\n**Recommended:**\n\nAdd a `test_integration.py` file to the `tests/integration` directory of the\nansible collection:\n\n```\n\"\"\"Tests for molecule scenarios.\"\"\"\nfrom __future__ import absolute_import, division, print_function\n\nfrom pytest_ansible.molecule import MoleculeScenario\n\n\ndef test_integration(molecule_scenario: MoleculeScenario) -> None:\n    \"\"\"Run molecule for each scenario.\n\n    :param molecule_scenario: The molecule scenario object\n    \"\"\"\n    proc = molecule_scenario.test()\n    assert proc.returncode == 0\n```\n\nThe `molecule_scenario` fixture provides parameterized molecule scenarios\ndiscovered in the collection's `extensions/molecule` directory, as well as other\ndirectories within the collection.\n\n`molecule test -s <scenario>` will be run for each scenario and a completed\nsubprocess returned from the `test()` call.\n\n**Legacy:**\n\nRun molecule with the `--molecule` command line parameter to inject each\nmolecule directory found in the current working directory. Each scenario will be\ninjected as an external test in the the tests available for pytest. Due to the\nnature of this approach, the molecule scenarios are not represented as python\ntests and may not show in the IDE's pytest test tree.\n\nTo run Molecule scenarios using pytest, follow these steps:\n\n1. Install the `pytest-ansible` plugin using pip:\n\n```\npip install pytest-ansible\n```\n\n2. Execute pytest to run Molecule scenarios: `pytest`\n\n## Ansible Integration for Pytest Tests\n\nThe `ansible_module`, `ansible_adhoc`, `localhost`, and `ansible_facts` fixtures\nare provided to help you integrate Ansible functionalities into your pytest\ntests. These fixtures allow you to interact with Ansible modules, run commands\non localhost, fetch Ansible facts, and more.\n\n## Fixtures and helpers for use in tests\n\nHere's a quick overview of the available fixtures:\n\n- `ansible_module`: Allows you to call Ansible modules directly within your test\n  functions.\n- `ansible_adhoc`: Provides a function to initialize a `HostManager` object to\n  work with Ansible inventory.\n- `localhost`: A convenience fixture for running Ansible modules that typically\n  run on the local machine.\n- `ansible_facts`: Returns a JSON structure representing system facts for the\n  associated inventory.\n\n### Usage\n\nOnce installed, the following `pytest` command-line parameters are available:\n\n```bash\npytest \\\n    [--inventory <path_to_inventory>] \\\n    [--extra-inventory <path_to_extra_inventory>] \\\n    [--host-pattern <host-pattern>] \\\n    [--connection <plugin>] \\\n    [--module-path <path_to_modules] \\\n    [--user <username>] \\\n    [--become] \\\n    [--become-user <username>] \\\n    [--become-method <method>] \\\n    [--ask-become-pass] \\\n    [--limit <limit>] \\\n    [--ansible-unit-inject-only] \\\n    [--molecule] \\\n    [--molecule-unavailable-driver] \\\n    [--skip-no-git-change] \\\n    [--check]\n```\n\n### Inventory\n\nUsing ansible first starts with defining your inventory. This can be done in\nseveral ways, but to start, we'll use the `ansible_adhoc` fixture.\n\n```python\ndef test_my_inventory(ansible_adhoc):\n    hosts = ansible_adhoc()\n```\n\nIn the example above, the `hosts` variable is an instance of the `HostManager`\nclass and describes your ansible inventory. For this to work, you'll need to\ntell `ansible` where to find your inventory. Inventory can be anything supported\nby ansible, which includes an\n[INI file](http://docs.ansible.com/ansible/latest/intro_inventory.html) or an\nexecutable script that returns\n[properly formatted JSON](http://docs.ansible.com/ansible/latest/intro_dynamic_inventory.html).\nFor example,\n\n```bash\npytest --inventory my_inventory.ini --host-pattern all\n```\n\nor\n\n```bash\npytest --inventory path/to/my/script.py --host-pattern webservers\n```\n\nor\n\n```bash\npytest --inventory one.example.com,two.example.com --host-pattern all\n```\n\nIn the above examples, the inventory provided at runtime will be used in all\ntests that use the `ansible_adhoc` fixture. A more realistic scenario may\ninvolve using different inventory files (or host patterns) with different tests.\nTo accomplish this, the fixture `ansible_adhoc` allows you to customize the\ninventory parameters. Read on for more detail on using the `ansible_adhoc`\nfixture.\n\n### Extra Inventory\n\nUsing ansible first starts with defining your extra inventory. This feature was\nadded in version 2.3.0, and is intended to allow the user to work with two\ndifferent inventories. This can be done in several ways, but to start, we'll use\nthe `ansible_adhoc` fixture.\n\nFor example,\n\n```bash\npytest --inventory my_inventory.ini --extra-inventory my_second_inventory.ini --host-pattern host_in_second_inventory\n```\n\n#### Fixture `ansible_adhoc`\n\nThe `ansible_adhoc` fixture returns a function used to initialize a\n`HostManager` object. The `ansible_adhoc` fixture will default to parameters\nsupplied to the `pytest` command-line, but also allows one to provide keyword\narguments used to initialize the inventory.\n\nThe example below demonstrates basic usage with options supplied at run-time to\n`pytest`.\n\n```python\ndef test_all_the_pings(ansible_adhoc):\n    ansible_adhoc().all.ping()\n```\n\nThe following example demonstrates available keyword arguments when creating a\n`HostManager` object.\n\n```python\ndef test_uptime(ansible_adhoc):\n    # take down the database\n    ansible_adhoc(inventory='db1.example.com,', user='ec2-user',\n        become=True, become_user='root').all.command('reboot')\n```\n\nThe `HostManager` object returned by the `ansible_adhoc()` function provides\nnumerous ways of calling ansible modules against some, or all, of the inventory.\nThe following demonstrates sample usage.\n\n```python\ndef test_host_manager(ansible_adhoc):\n    hosts = ansible_adhoc()\n\n    # __getitem__\n    hosts['all'].ping()\n    hosts['localhost'].ping()\n\n    # __getattr__\n    hosts.all.ping()\n    hosts.localhost.ping()\n\n    # Supports [ansible host patterns](http://docs.ansible.com/ansible/latest/intro_patterns.html)\n    hosts['webservers:!phoenix'].ping()  # all webservers that are not in phoenix\n    hosts[0].ping()\n    hosts[0:2].ping()\n\n    assert 'one.example.com' in hosts\n\n    assert hasattr(hosts, 'two.example.com')\n\n    for a_host in hosts:\n        a_host.ping()\n```\n\n#### Fixture `localhost`\n\nThe `localhost` fixture is a convenience fixture that surfaces a\n`ModuleDispatcher` instance for ansible host running `pytest`. This is\nconvenient when using ansible modules that typically run on the local machine,\nsuch as cloud modules (ec2, gce etc...).\n\n```python\ndef test_do_something_cloudy(localhost, ansible_adhoc):\n    \"\"\"Deploy an ec2 instance using multiple fixtures.\"\"\"\n    params = dict(\n        key_name='some_key',\n        instance_type='t2.micro',\n        image='ami-123456',\n        wait=True,\n        group='webservers',\n        count=1,\n        vpc_subnet_id='subnet-29e63245',\n        assign_public_ip=True,\n    )\n\n    # Deploy an ec2 instance from localhost using the `ansible_adhoc` fixture\n    ansible_adhoc(inventory='localhost,', connection='local').localhost.ec2(**params)\n\n    # Deploy an ec2 instance from localhost using the `localhost` fixture\n    localhost.ec2(**params)\n```\n\n#### Fixture `ansible_module`\n\nThe `ansible_module` fixture allows tests and fixtures to call\n[ansible modules](http://docs.ansible.com/modules.html). Unlike the\n`ansible_adhoc` fixture, this fixture only uses the options supplied to `pytest`\nat run time.\n\nA very basic example demonstrating the ansible\n[`ping` module](http://docs.ansible.com/ping_module.html):\n\n```python\ndef test_ping(ansible_module):\n    ansible_module.ping()\n```\n\nA more involved example of updating the sshd configuration, and restarting the\nservice.\n\n```python\ndef test_sshd_config(ansible_module):\n\n    # update sshd MaxSessions\n    contacted = ansible_module.lineinfile(\n        dest=\"/etc/ssh/sshd_config\",\n        regexp=\"^#?MaxSessions .*\",\n        line=\"MaxSessions 150\")\n    )\n\n    # assert desired outcome\n    for (host, result) in contacted.items():\n        assert 'failed' not in result, result['msg']\n        assert 'changed' in result\n\n    # restart sshd\n    contacted = ansible_module.service(\n        name=\"sshd\",\n        state=\"restarted\"\n    )\n\n    # assert successful restart\n    for (host, result) in contacted.items():\n        assert 'changed' in result and result['changed']\n        assert result['name'] == 'sshd'\n\n    # do other stuff ...\n```\n\n#### Fixture `ansible_facts`\n\nThe `ansible_facts` fixture returns a JSON structure representing the system\nfacts for the associated inventory. Sample fact data is available in the\n[ansible documentation](http://docs.ansible.com/playbooks_variables.html#information-discovered-from-systems-facts).\n\nNote, this fixture is provided for convenience and could easily be called using\n`ansible_module.setup()`.\n\nA systems facts can be useful when deciding whether to skip a test ...\n\n```python\ndef test_something_with_amazon_ec2(ansible_facts):\n    for facts in ansible_facts:\n        if 'ec2.internal' != facts['ansible_domain']:\n            pytest.skip(\"This test only applies to ec2 instances\")\n\n```\n\nAdditionally, since facts are just ansible modules, you could inspect the\ncontents of the `ec2_facts` module for greater granularity ...\n\n```python\n\ndef test_terminate_us_east_1_instances(ansible_adhoc):\n\n    for facts in ansible_adhoc().all.ec2_facts():\n        if facts['ansible_ec2_placement_region'].startswith('us-east'):\n            '''do some testing'''\n```\n\n#### Parameterize with `pytest.mark.ansible`\n\nPerhaps the `--ansible-inventory=<inventory>` includes many systems, but you\nonly wish to interact with a subset. The `pytest.mark.ansible` marker can be\nused to modify the `pytest-ansible` command-line parameters for a single test.\nPlease note, the fixture `ansible_adhoc` is the prefer mechanism for interacting\nwith ansible inventory within tests.\n\nFor example, to interact with the local system, you would adjust the\n`host_pattern` and `connection` parameters.\n\n```python\n@pytest.mark.ansible(host_pattern='local,', connection='local')\ndef test_copy_local(ansible_module):\n\n    # create a file with random data\n    contacted = ansible_module.copy(\n        dest='/etc/motd',\n        content='PyTest is amazing!',\n        owner='root',\n        group='root',\n        mode='0644',\n    )\n\n    # assert only a single host was contacted\n    assert len(contacted) == 1, \\\n        \"Unexpected number of hosts contacted (%d != %d)\" % \\\n        (1, len(contacted))\n\n    assert 'local' in contacted\n\n    # assert the copy module reported changes\n    assert 'changed' in contacted['local']\n    assert contacted['local']['changed']\n```\n\nNote, the parameters provided by `pytest.mark.ansible` will apply to all class\nmethods.\n\n```python\n@pytest.mark.ansible(host_pattern='local,', connection='local')\nclass Test_Local(object):\n    def test_install(self, ansible_module):\n        '''do some testing'''\n    def test_template(self, ansible_module):\n        '''do some testing'''\n    def test_service(self, ansible_module):\n        '''do some testing'''\n```\n\n#### Inspecting results\n\nWhen using the `ansible_adhoc`, `localhost` or `ansible_module` fixtures, the\nobject returned will be an instance of class `AdHocResult`. The `AdHocResult`\nclass can be inspected as follows:\n\n```python\n\ndef test_adhoc_result(ansible_adhoc):\n    contacted = ansible_adhoc(inventory=my_inventory).command(\"date\")\n\n    # As a dictionary\n    for (host, result) in contacted.items():\n        assert result.is_successful, \"Failed on host %s\" % host\n    for result in contacted.values():\n        assert result.is_successful\n    for host in contacted.keys():\n        assert host in ['localhost', 'one.example.com']\n\n    assert contacted.localhost.is_successful\n\n    # As a list\n    assert len(contacted) > 0\n    assert 'localhost' in contacted\n\n    # As an iterator\n    for result in contacted:\n        assert result.is_successful\n\n    # With __getattr__\n    assert contacted.localhost.is_successful\n\n    # Or __getitem__\n    assert contacted['localhost'].is_successful\n```\n\nUsing the `AdHocResult` object provides ways to conveniently access results for\ndifferent hosts involved in the ansible adhoc command. Once the specific host\nresult is found, you may inspect the result of the ansible adhoc command on that\nuse by way of the `ModuleResult` interface. The `ModuleResult` class represents\nthe dictionary returned by the ansible module for a particular host. The\ncontents of the dictionary depend on the module called.\n\nThe `ModuleResult` interface provides some convenient properties to determine\nthe success of the module call. Examples are included below.\n\n```python\n\ndef test_module_result(localhost):\n    contacted = localhost.command(\"find /tmp\")\n\n    assert contacted.localhost.is_successful\n    assert contacted.localhost.is_ok\n    assert contacted.localhost.is_changed\n    assert not contacted.localhost.is_failed\n\n    contacted = localhost.shell(\"exit 1\")\n    assert contacted.localhost.is_failed\n    assert not contacted.localhost.is_successful\n```\n\nThe contents of the JSON returned by an ansible module differs from module to\nmodule. For guidance, consult the documentation and examples for the specific\n[ansible module](http://docs.ansible.com/modules_by_category.html).\n\n#### Exception handling\n\nIf `ansible` is unable to connect to any inventory, an exception will be raised.\n\n```python\n@pytest.mark.ansible(inventory='unreachable.example.com,')\ndef test_shutdown(ansible_module):\n\n    # attempt to ping a host that is down (or doesn't exist)\n    pytest.raises(pytest_ansible.AnsibleHostUnreachable):\n        ansible_module.ping()\n```\n\nSometimes, only a single host is unreachable, and others will have properly\nreturned data. The following demonstrates how to catch the exception, and\ninspect the results.\n\n```python\n@pytest.mark.ansible(inventory='good:bad')\ndef test_inventory_unreachable(ansible_module):\n    exc_info = pytest.raises(pytest_ansible.AnsibleHostUnreachable, ansible_module.ping)\n    (contacted, dark) = exc_info.value.results\n\n    # inspect the JSON result...\n    for (host, result) in contacted.items():\n        assert result['ping'] == 'pong'\n\n    for (host, result) in dark.items():\n        assert result['failed'] == True\n```\n\n## Contributing\n\nContributions are very welcome. Tests can be run with\n[tox](https://tox.wiki/en/latest/), please ensure the coverage at least stays\nthe same before you submit a pull request.\n\n## License\n\nDistributed under the terms of the [MIT](https://opensource.org/license/mit/)\nlicense, \"pytest-ansible\" is free and open source software\n\n## Issues\n\nIf you encounter any problems, please\n[file an issue](https://github.com/pycontribs/pytest-ansible/issues) along with\na detailed description.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Plugin for pytest to simplify calling ansible modules from tests or fixtures",
    "version": "24.1.2",
    "project_urls": {
        "changelog": "https://github.com/ansible/pytest-ansible/releases",
        "documentation": "https://github.com/ansible/pytest-ansible",
        "homepage": "https://github.com/ansible/pytest-ansible",
        "repository": "https://github.com/ansible/pytest-ansible"
    },
    "split_keywords": [
        "ansible",
        "testing",
        "pytest"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "3d4a2b5f4637a9b04e1fed4fb4e9c221fc032f8e1b55c71e6f905486782f1f9c",
                "md5": "729219d43835b638fcfa190ccc3f3342",
                "sha256": "5a3fa6443c344397e35f867e131ee0454ba935213565a19ebff1d620daae15f5"
            },
            "downloads": -1,
            "filename": "pytest_ansible-24.1.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "729219d43835b638fcfa190ccc3f3342",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 30586,
            "upload_time": "2024-01-18T11:38:05",
            "upload_time_iso_8601": "2024-01-18T11:38:05.753045Z",
            "url": "https://files.pythonhosted.org/packages/3d/4a/2b5f4637a9b04e1fed4fb4e9c221fc032f8e1b55c71e6f905486782f1f9c/pytest_ansible-24.1.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e1b9b0adb889c629ca37aef8629c480914ac25db89ca753da7ca30d3d24df096",
                "md5": "a568b53d1159648c8bf8300da2f9e222",
                "sha256": "28602a55c73390b2b22ea779833ea4af0a1b68fc39ebdc8c37cfb68041536ac9"
            },
            "downloads": -1,
            "filename": "pytest-ansible-24.1.2.tar.gz",
            "has_sig": false,
            "md5_digest": "a568b53d1159648c8bf8300da2f9e222",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 55289,
            "upload_time": "2024-01-18T11:38:07",
            "upload_time_iso_8601": "2024-01-18T11:38:07.137362Z",
            "url": "https://files.pythonhosted.org/packages/e1/b9/b0adb889c629ca37aef8629c480914ac25db89ca753da7ca30d3d24df096/pytest-ansible-24.1.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-18 11:38:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ansible",
    "github_project": "pytest-ansible",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "pytest-ansible"
}
        
Elapsed time: 0.17089s