Skip to content

Commit

Permalink
add unit test for the cli
Browse files Browse the repository at this point in the history
  • Loading branch information
Sispheor committed Nov 25, 2022
1 parent 690e7d1 commit c022e57
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 11 deletions.
48 changes: 48 additions & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Build

## Ansible collection

Build the collection
```
ansible-galaxy collection build
```

Push to Galaxy:
```
ansible-galaxy collection publish <built_tarball>
```

E.g:
```
ansible-galaxy collection publish hpe-monkeyble-1.0.3.tar.gz
```

## Python package

The build package need to be present
```
pip3 install build
```

Build the CLI:
```
python -m build
```

This command creates a file in `dist/`


Publish to test env Pypi:
```
python3 -m twine upload --repository testpypi -u sispheor -p $PYPI_PASSWORD dist/*
```

Publish to prod env Pypi:
```
python3 -m twine upload --repository pypi dist/*
```

Test installing with pipx
```
pipx inject ansible --index-url https://test.pypi.org/simple/ --include-apps monkeyble
```
2 changes: 2 additions & 0 deletions monkeyble/cli/const.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
MONKEYBLE = "Monkeyble"
TEST_PASSED = "PASSED"
TEST_FAILED = "FAILED"
MONKEYBLE_DEFAULT_CONFIG_PATH = "monkeyble.yml"
MONKEYBLE_DEFAULT_ANSIBLE_CMD = "ansible-playbook"
15 changes: 15 additions & 0 deletions monkeyble/cli/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2022 Hewlett Packard Enterprise Development LP
import sys

from ansible.utils.display import Display
from ansible import constants as C
from monkeyble.cli.utils import Utils

global_display = Display()


class MonkeybleCLIException(Exception):
def __init__(self, message, exit_code=1):
super().__init__(message)
Utils.print_danger(message)
sys.exit(exit_code)
21 changes: 12 additions & 9 deletions monkeyble/cli/monkeyble_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
import yaml
from tabulate import tabulate

from monkeyble.cli.const import *
from monkeyble.cli.const import MONKEYBLE_DEFAULT_CONFIG_PATH, TEST_PASSED, TEST_FAILED, MONKEYBLE, \
MONKEYBLE_DEFAULT_ANSIBLE_CMD
from monkeyble.cli.exceptions import MonkeybleCLIException
from monkeyble.cli.models import MonkeybleResult, ScenarioResult
from monkeyble.cli.utils import Utils

logger = logging.getLogger(MONKEYBLE)

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(message)s')

# actions available
ACTION_LIST = ["test"]

Expand All @@ -38,20 +42,19 @@ def run_ansible(ansible_cmd, playbook, inventory, extra_vars, scenario):
stderr=subprocess.STDOUT)
for line in iter(pipes.stdout.readline, b''):
print(f"{line.rstrip().decode('utf-8')}")
std_out, std_err = pipes.communicate()
pipes.wait()
if pipes.returncode == 0:
return TEST_PASSED
else:
return TEST_FAILED


def run_monkeyble_test(monkeyble_config):
ansible_cmd = "ansible-playbook"
ansible_cmd = MONKEYBLE_DEFAULT_ANSIBLE_CMD
if "ansible_cmd" in monkeyble_config:
ansible_cmd = monkeyble_config["ansible_cmd"]
if "monkeyble_tests" not in monkeyble_config:
Utils.print_danger("No 'monkeyble_tests' variable defined")
sys.exit(1)
raise MonkeybleCLIException(message="No 'monkeyble_tests' variable defined")
list_result = list()
for test_config in monkeyble_config["monkeyble_tests"]:
if "ansible_cmd" in test_config:
Expand All @@ -60,13 +63,12 @@ def run_monkeyble_test(monkeyble_config):
playbook = test_config.get("playbook", None)
new_result = MonkeybleResult(playbook)
if playbook is None:
Utils.print_danger("Missing 'playbook' key in a test")
sys.exit(1)
raise MonkeybleCLIException(message="Missing 'playbook' key in a test")
inventory = test_config.get("inventory", None)
extra_vars = test_config.get("extra_vars", None)
scenarios = test_config.get("scenarios", None)
if scenarios is None:
Utils.print_danger("No scenario selected")
raise MonkeybleCLIException(message=f"No scenarios for playbook {playbook}")
# print the current path
Utils.print_info(f"Monkeyble - current path: {pathlib.Path().resolve()}")
list_scenario_result = list()
Expand Down Expand Up @@ -124,14 +126,15 @@ def load_monkeyble_config(arg_config_path):
- cli args 'config'
"""
# set a default config
config_path = "monkeyble.yml"
config_path = MONKEYBLE_DEFAULT_CONFIG_PATH
# load from env if exist
env_config = os.getenv("MONKEYBLE_CONFIG", default=None)
if env_config is not None:
config_path = env_config
# load from cli args
if arg_config_path is not None:
config_path = arg_config_path
logger.debug(f"Try to open file {config_path}")
with open(config_path, "r") as stream:
try:
monkeyble_config = yaml.full_load(stream)
Expand Down
11 changes: 10 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
from os import path

from setuptools import setup, find_packages

# Get the long description from the README file
basedir = path.abspath(path.dirname(__file__))
with open(path.join(basedir, 'README.md'), encoding='utf-8') as f:
long_description = f.read()

setup(
name='monkeyble',
description='End-to-end testing framework for Ansible',
version='1.1.0b',
version='1.1.0.dev0',
long_description=long_description,
long_description_content_type='text/markdown',
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
url='https://hewlettpackard.github.io/monkeyble/',
license='GNU General Public License v3 (GPLv3)',
Expand Down
2 changes: 1 addition & 1 deletion tests/mocks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
test_input_config_1:
- assert_equal:
arg_name: msg
expected: "Hello Monkeyble"
expected: "Goodbye Monkeyble"

# mocks
replace_debug_mock:
Expand Down
132 changes: 132 additions & 0 deletions tests/units/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import io
import os
import unittest
from unittest import mock
from unittest.mock import patch, mock_open, call

from monkeyble.cli.const import TEST_PASSED, TEST_FAILED, MONKEYBLE_DEFAULT_ANSIBLE_CMD
from monkeyble.cli.exceptions import MonkeybleCLIException

from monkeyble.cli.models import MonkeybleResult, ScenarioResult

from monkeyble.cli.monkeyble_cli import load_monkeyble_config, do_exit, run_monkeyble_test, run_ansible


class TestMonkeybleModule(unittest.TestCase):

def test_load_monkeyble_config_default_config(self):
with patch("builtins.open", mock_open(read_data="data")) as mock_open_file:
load_monkeyble_config(None)
mock_open_file.assert_called_with("monkeyble.yml", 'r')

@mock.patch.dict(os.environ, {"MONKEYBLE_CONFIG": "monkeyble_from_env.yml"})
def test_load_monkeyble_config_from_env(self):
with patch("builtins.open", mock_open(read_data="data")) as mock_open_file:
load_monkeyble_config(None)
mock_open_file.assert_called_with("monkeyble_from_env.yml", 'r')

def test_load_monkeyble_config_from_args(self):
with patch("builtins.open", mock_open(read_data="data")) as mock_open_file:
load_monkeyble_config("/path/to/monkeyble.yml")
mock_open_file.assert_called_with("/path/to/monkeyble.yml", 'r')

@patch("monkeyble.cli.monkeyble_cli.MONKEYBLE_DEFAULT_CONFIG_PATH", "test_config/monkeyble.yml")
def test_load_monkeyble_config(self):
data = load_monkeyble_config(None)
expected = {'ansible_cmd': 'ansible-playbook -v',
'monkeyble_tests': [{'playbook': 'test_playbook.yml',
'inventory': 'inventory',
'extra_vars': ['mocks.yml', 'monkeyble_scenarios.yml'],
'scenarios': ['validate_test_1', 'validate_test_2']}]}

self.assertDictEqual(data, expected)

@patch('sys.exit')
def test_do_exit_all_test_passed(self, mock_exit):
scenario_result1 = ScenarioResult(scenario="scenario1", result=TEST_PASSED)
scenario_result2 = ScenarioResult(scenario="scenario2", result=TEST_PASSED)
monkeyble_result = MonkeybleResult(playbook="playbook", scenario_results=[scenario_result1, scenario_result2])

do_exit([monkeyble_result])
mock_exit.assert_called_with(0)

@patch('sys.exit')
def test_do_exit_all_test_failed(self, mock_exit):
scenario_result1 = ScenarioResult(scenario="scenario1", result=TEST_PASSED)
scenario_result2 = ScenarioResult(scenario="scenario2", result=TEST_FAILED)
monkeyble_result = MonkeybleResult(playbook="playbook", scenario_results=[scenario_result1, scenario_result2])

do_exit([monkeyble_result])
mock_exit.assert_called_with(1)

@patch('sys.exit')
def test_run_monkeyble_test_no_tests_defined(self, mock_exit):
monkeyble_config = dict()
with self.assertRaises(MonkeybleCLIException):
run_monkeyble_test(monkeyble_config)
mock_exit.assert_called_with(1)

@patch('sys.exit')
def test_run_monkeyble_test_no_playbook_defined(self, mock_exit):
monkeyble_config = {
"monkeyble_tests": [
{"inventory": "test"}
]
}
with self.assertRaises(MonkeybleCLIException):
run_monkeyble_test(monkeyble_config)
mock_exit.assert_called_with(1)

@patch('sys.exit')
def test_run_monkeyble_test_no_scenario_defined(self, mock_exit):
monkeyble_config = {
"monkeyble_tests": [
{"playbook": "playbook.yml"}
]
}
with self.assertRaises(MonkeybleCLIException):
run_monkeyble_test(monkeyble_config)
mock_exit.assert_called_with(1)

def test_run_monkeyble_test_run_ansible_called(self):
monkeyble_config = {
"monkeyble_tests": [
{
"playbook": "playbook.yml",
"inventory": "my_inventory",
"extra_vars": ["extra_vars1.yml", "extra_vars2.yml"],
"scenarios": ["scenario1", "scenario2"]
}
]
}

with mock.patch("monkeyble.cli.monkeyble_cli.run_ansible") as mock_run_ansible:
run_monkeyble_test(monkeyble_config)
self.assertEqual(mock_run_ansible.call_count, 2)
call_1 = call(MONKEYBLE_DEFAULT_ANSIBLE_CMD,
"playbook.yml",
"my_inventory",
["extra_vars1.yml", "extra_vars2.yml"],
"scenario1")
call_2 = call(MONKEYBLE_DEFAULT_ANSIBLE_CMD,
"playbook.yml",
"my_inventory",
["extra_vars1.yml", "extra_vars2.yml"],
"scenario2")
mock_run_ansible.assert_has_calls([call_1, call_2])

@patch("subprocess.Popen")
def test_run_ansible(self, mock_subproc_popen):
mock_subproc_popen.return_value.stdout = io.BytesIO(b"playbook output")

run_ansible(MONKEYBLE_DEFAULT_ANSIBLE_CMD,
"playbook.yml",
"my_inventory",
["extra_vars1.yml", "extra_vars2.yml"],
"scenario1")
self.assertTrue(mock_subproc_popen.called)

expected_call = call(['ansible-playbook', 'playbook.yml', '-i', 'my_inventory',
'-e', '@extra_vars1.yml', '-e', '@extra_vars2.yml',
'-e', 'monkeyble_scenario=scenario1'], stdout=-1, stderr=-2)
mock_subproc_popen.assert_has_calls([expected_call])
10 changes: 10 additions & 0 deletions tests/units/test_config/monkeyble.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
ansible_cmd: "ansible-playbook -v"
monkeyble_tests:
- playbook: "test_playbook.yml"
inventory: "inventory"
extra_vars:
- "mocks.yml"
- "monkeyble_scenarios.yml"
scenarios:
- validate_test_1
- validate_test_2

0 comments on commit c022e57

Please sign in to comment.