From 23ed05c263170bab542a7209693ef535b5e6d656 Mon Sep 17 00:00:00 2001 From: Nilashish Chakraborty Date: Tue, 2 Jan 2024 17:21:00 +0530 Subject: [PATCH] Add integration tests (#50) --- .config/requirements-test.txt | 1 + .github/workflows/tox.yml | 1 + README.md | 4 +- src/ansible_creator/templar.py | 16 ---- src/ansible_creator/validators.py | 80 ----------------- tests/conftest.py | 32 +++++++ tests/{.keep => integration/__init__.py} | 0 tests/integration/test_init.py | 108 +++++++++++++++++++++++ tests/units/conftest.py | 6 -- tests/units/test_init.py | 6 +- tox.ini | 2 +- 11 files changed, 148 insertions(+), 108 deletions(-) delete mode 100644 src/ansible_creator/validators.py create mode 100644 tests/conftest.py rename tests/{.keep => integration/__init__.py} (100%) create mode 100644 tests/integration/test_init.py delete mode 100644 tests/units/conftest.py diff --git a/.config/requirements-test.txt b/.config/requirements-test.txt index 34c8f0b4..b24fcf4c 100644 --- a/.config/requirements-test.txt +++ b/.config/requirements-test.txt @@ -1,4 +1,5 @@ coverage pytest +coverage[toml] pytest-xdist tox diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index d1235510..8815c6bd 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -77,6 +77,7 @@ jobs: - name: "tox -e ${{ matrix.passed_name }}" continue-on-error: ${{ matrix.devel || false }} run: python3 -m tox -e ${{ matrix.passed_name }} + - name: Archive logs uses: actions/upload-artifact@v3 with: diff --git a/README.md b/README.md index 13c88d97..a7bed6ee 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,6 @@ $ tree -lla /home/ansible-dev/collections/ansible_collections ## Licensing -GNU General Public License v3.0 or later. +ansible-creator is released under the Apache License version 2. -See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. +See the [LICENSE](https://github.com/ansible/ansible-creator/blob/main/LICENSE) file for more details. diff --git a/src/ansible_creator/templar.py b/src/ansible_creator/templar.py index 82af2031..8e07cf5b 100644 --- a/src/ansible_creator/templar.py +++ b/src/ansible_creator/templar.py @@ -9,8 +9,6 @@ except ImportError: HAS_JINJA2 = False -from ansible_creator.utils import get_file_contents - class Templar: """Class representing a Jinja2 template engine.""" @@ -33,20 +31,6 @@ def __init__(self: Templar) -> None: keep_trailing_newline=True, ) - def render(self: Templar, template_name: str, data: dict) -> str: - """Load template from a file and render with provided data. - - :param template_name: Name of the template to load. - :param data: Data to render template with. - - :returns: Templated content. - """ - template_content = get_file_contents( - directory="templates", - filename=template_name, - ) - return self.render_from_content(template=template_content, data=data) - def render_from_content(self: Templar, template: str, data: dict) -> str: """Render a template with provided data. diff --git a/src/ansible_creator/validators.py b/src/ansible_creator/validators.py deleted file mode 100644 index e7a343a5..00000000 --- a/src/ansible_creator/validators.py +++ /dev/null @@ -1,80 +0,0 @@ -"""A schema validation helper for ansible-creator.""" - -from __future__ import annotations - -from json import JSONDecodeError, loads - - -try: - from jsonschema import SchemaError - from jsonschema.validators import validator_for - - HAS_JSONSCHEMA = True -except ImportError: - HAS_JSONSCHEMA = False - -from ansible_creator.exceptions import CreatorError -from ansible_creator.utils import get_file_contents - - -class SchemaValidator: - """Class representing a validation engine for ansible-creator content definition files.""" - - def __init__(self: SchemaValidator, data: dict, criteria: str) -> None: - """Initialize the validation engine. - - :param data: Content definition as a dictionary - :param criteria: Name of the file that contains the JSON schema. - """ - self.data = data - self.criteria = criteria - - def validate(self: SchemaValidator) -> list: - """Validate data against loaded schema. - - :returns: A list of schema validation errors (if any). - :raises CreatorError: if sanity check for schema fails. - """ - errors = [] - schema = self.load_schema() - validator = validator_for(schema) - - try: - validator.check_schema(schema) - except SchemaError as schema_err: - msg = "Sanity check failed for in-built schema. This is likely a bug.\n" - raise CreatorError( - msg, - ) from schema_err - - validation_errors = sorted( - validator(schema).iter_errors(self.data), - key=lambda e: e.path, - ) - - for err in validation_errors: - err_msg = "* " + str(err.message) + " at " + str(err.instance) + "\n" - errors.append(err_msg) - - return errors - - def load_schema(self: SchemaValidator) -> dict: - """Attempt to load the schema from schemas directory. - - :returns: A schema loaded as json. - :raises CreatorError: if the JSON schema cannot be loaded. - """ - try: - schema: dict = loads(get_file_contents("schemas", self.criteria)) - except ( - FileNotFoundError, - TypeError, - JSONDecodeError, - ModuleNotFoundError, - ) as err: - msg = "Unable to load jsonschema for validation with error(s):\n" - raise CreatorError( - msg, - ) from err - - return schema diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..1e06154c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,32 @@ +"""conftest.""" +import os +import subprocess +import pytest + +os.environ["HOME"] = "/home/ansible" +os.environ["DEV_WORKSPACE"] = "collections/ansible_collections" + + +@pytest.fixture +def cli(): + """fixture to run CLI commands.""" + return cli_run + + +def cli_run(args): + """execute a command using subprocess.""" + updated_env = os.environ.copy() + # this helps asserting stdout/stderr + updated_env.update({"LINES": "40", "COLUMNS": "300", "TERM": "xterm-256color"}) + try: + result = subprocess.run( + args, + shell=True, + capture_output=True, + check=True, + text=True, + env=updated_env, + ) + return result + except subprocess.CalledProcessError as err: + return err diff --git a/tests/.keep b/tests/integration/__init__.py similarity index 100% rename from tests/.keep rename to tests/integration/__init__.py diff --git a/tests/integration/test_init.py b/tests/integration/test_init.py new file mode 100644 index 00000000..9a5f2513 --- /dev/null +++ b/tests/integration/test_init.py @@ -0,0 +1,108 @@ +"""Unit tests for ansible-creator init.""" + +from __future__ import annotations + +import re +import sys +from textwrap import dedent + + +def test_run_help(cli): + result = cli("ansible-creator --help") + assert result.returncode == 0 + + # temporary assertion fix until we write custom helper + if sys.version_info < (3, 10): + assert ( + dedent( + """\ + usage: ansible-creator [-h] [--version] {init} ... + + Tool to scaffold Ansible Content. Get started by looking at the help text. + + optional arguments: + -h, --help show this help message and exit + --version Print ansible-creator version and exit. + + Commands: + {init} The subcommand to invoke. + init Initialize an Ansible Collection. + """, + ) + in result.stdout + ) + else: + assert ( + dedent( + """\ + usage: ansible-creator [-h] [--version] {init} ... + + Tool to scaffold Ansible Content. Get started by looking at the help text. + + options: + -h, --help show this help message and exit + --version Print ansible-creator version and exit. + + Commands: + {init} The subcommand to invoke. + init Initialize an Ansible Collection. + """, + ) + in result.stdout + ) + + +def test_run_no_subcommand(cli): + result = cli("ansible-creator") + assert result.returncode != 0 + assert ( + dedent( + """\ + usage: ansible-creator [-h] [--version] {init} ... + ansible-creator: error: the following arguments are required: subcommand + """, + ) + in result.stderr + ) + + +def test_run_init_no_input(cli): + result = cli("ansible-creator init") + assert result.returncode != 0 + assert ( + "ansible-creator init: error: the following arguments are required: collection" + in result.stderr + ) + + +def test_run_init_basic(cli, tmp_path): + result = cli(f"ansible-creator init testorg.testcol --init-path {tmp_path}") + assert result.returncode == 0 + + # check stdout + assert ( + re.search("Note: collection testorg.testcol created at", result.stdout) + is not None + ) + + # fail to override existing collection with force=false (default) + result = cli(f"ansible-creator init testorg.testcol --init-path {tmp_path}") + assert result.returncode != 0 + assert ( + re.search( + rf"Error: The directory\s+{tmp_path}/testorg/testcol\s+already exists.", + result.stderr, + flags=re.MULTILINE, + ) + is not None + ) + assert "You can use --force to re-initialize this directory." in result.stderr + assert "However it will delete ALL existing contents in it." in result.stderr + + # override existing collection with force=true + result = cli(f"ansible-creator init testorg.testcol --init-path {tmp_path} --force") + assert result.returncode == 0 + assert ( + re.search("Warning: re-initializing existing directory", result.stdout) + is not None + ) diff --git a/tests/units/conftest.py b/tests/units/conftest.py deleted file mode 100644 index 40c2e16f..00000000 --- a/tests/units/conftest.py +++ /dev/null @@ -1,6 +0,0 @@ -"""conftest.""" -import os - - -os.environ["HOME"] = "/home/ansible" -os.environ["DEV_WORKSPACE"] = "collections/ansible_collections" diff --git a/tests/units/test_init.py b/tests/units/test_init.py index e59e2f26..c5589b5e 100644 --- a/tests/units/test_init.py +++ b/tests/units/test_init.py @@ -18,7 +18,7 @@ @pytest.fixture() -def cli_args(tmp_path: Path) -> dict: +def cli_args(tmp_path) -> dict: """Create an Init class object as fixture. :param tmp_path: App configuration object. @@ -38,7 +38,7 @@ def cli_args(tmp_path: Path) -> dict: @pytest.fixture() -def output(tmp_path: Path) -> Output: +def output(tmp_path) -> Output: """Create an Output class object as fixture. :param tmp_path: App configuration object. @@ -69,7 +69,7 @@ def test_run_success( result = capsys.readouterr().out # check stdout - assert re.search("Note: collection testorg.testcol created at", result) is not None + assert re.search("Note: collection testorg.testcol created", result) is not None # recursively assert files created dircmp(str(tmp_path), str(FIXTURES_DIR / "collection")).report_full_closure() diff --git a/tox.ini b/tox.ini index 3ce22008..37447f7c 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ commands_pre = commands = coverage run -m pytest {posargs:tests} sh -c "coverage combine -a -q --data-file=.coverage {toxworkdir}/.coverage.*" - -sh -c "COVERAGE_FILE= coverage xml --ignore-errors -q --fail-under=0" + sh -c "COVERAGE_FILE= coverage xml --ignore-errors -q --fail-under=0" sh -c "COVERAGE_FILE= coverage report" allowlist_externals = sh