Migrate tests to unittest

unittest is much more straightforward without any magic. In a small
project like podman-compose being easy to understand is more important
than features.

Signed-off-by: Povilas Kanapickas <povilas@radix.lt>
This commit is contained in:
Povilas Kanapickas 2024-03-08 11:00:34 +02:00
parent 7539257ee8
commit 23fe9e7e1d
17 changed files with 806 additions and 880 deletions

View File

@ -25,15 +25,9 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
- name: Lint with flake8 - name: Test with unittest
run: | run: |
# stop the build if there are Python syntax errors or undefined names coverage run --source podman_compose -m unittest pytests/*.py
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics python -m unittest tests/*.py
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
coverage run --source podman_compose -m pytest ./pytests
python -m pytest ./tests
coverage combine coverage combine
coverage report coverage report

View File

@ -41,8 +41,8 @@ $ pre-commit run --all-files
``` ```
6. Run code coverage 6. Run code coverage
```shell ```shell
coverage run --source podman_compose -m pytest ./pytests coverage run --source podman_compose -m unittest pytests/*.py
python -m pytest ./tests python -m unittest tests/*.py
coverage combine coverage combine
coverage report coverage report
coverage html coverage html

View File

@ -103,11 +103,11 @@ There is also AWX 17.1.0
Inside `tests/` directory we have many useless docker-compose stacks Inside `tests/` directory we have many useless docker-compose stacks
that are meant to test as many cases as we can to make sure we are compatible that are meant to test as many cases as we can to make sure we are compatible
### Unit tests with pytest ### Unit tests with unittest
run a pytest with following command run a unittest with following command
```shell ```shell
python -m pytest pytests python -m unittest pytests/*.py
``` ```
# Contributing guide # Contributing guide

0
pytests/__init__.py Normal file
View File

View File

@ -2,10 +2,13 @@ import copy
import os import os
import argparse import argparse
import yaml import yaml
import unittest
from parameterized import parameterized
from podman_compose import normalize_service, PodmanCompose from podman_compose import normalize_service, PodmanCompose
test_cases_simple = [ class TestMergeBuild(unittest.TestCase):
@parameterized.expand([
({"test": "test"}, {"test": "test"}), ({"test": "test"}, {"test": "test"}),
({"build": "."}, {"build": {"context": "."}}), ({"build": "."}, {"build": {"context": "."}}),
({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}), ({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}),
@ -18,22 +21,11 @@ test_cases_simple = [
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
), ),
] ])
def test_simple(self, input, expected):
self.assertEqual(normalize_service(input), expected)
@parameterized.expand([
def test_normalize_service_simple():
for test_case, expected in copy.deepcopy(test_cases_simple):
test_original = copy.deepcopy(test_case)
test_case = normalize_service(test_case)
test_result = expected == test_case
if not test_result:
print("test: ", test_original)
print("expected: ", expected)
print("actual: ", test_case)
assert test_result
test_cases_sub_dir = [
({"test": "test"}, {"test": "test"}), ({"test": "test"}, {"test": "test"}),
({"build": "."}, {"build": {"context": "./sub_dir/."}}), ({"build": "."}, {"build": {"context": "./sub_dir/."}}),
({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}), ({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}),
@ -46,22 +38,11 @@ test_cases_sub_dir = [
{"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}},
{"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}}, {"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}},
), ),
] ])
def test_normalize_service_with_sub_dir(self, input, expected):
self.assertEqual(normalize_service(input, sub_dir="./sub_dir"), expected)
@parameterized.expand([
def test_normalize_service_with_sub_dir():
for test_case, expected in copy.deepcopy(test_cases_sub_dir):
test_original = copy.deepcopy(test_case)
test_case = normalize_service(test_case, sub_dir="./sub_dir")
test_result = expected == test_case
if not test_result:
print("test: ", test_original)
print("expected: ", expected)
print("actual: ", test_case)
assert test_result
test_cases_merges = [
({}, {}, {}), ({}, {}, {}),
({}, {"test": "test"}, {"test": "test"}), ({}, {"test": "test"}, {"test": "test"}),
({"test": "test"}, {}, {"test": "test"}), ({"test": "test"}, {}, {"test": "test"}),
@ -116,13 +97,10 @@ test_cases_merges = [
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}}, {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}},
{"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "ENV2=2"]}}, {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "ENV2=2"]}},
), ),
] ])
def test_parse_compose_file_when_multiple_composes(self, input, override, expected):
compose_test_1 = {"services": {"test-service": input}}
def test__parse_compose_file_when_multiple_composes() -> None: compose_test_2 = {"services": {"test-service": override}}
for test_input, test_override, expected_result in copy.deepcopy(test_cases_merges):
compose_test_1 = {"services": {"test-service": test_input}}
compose_test_2 = {"services": {"test-service": test_override}}
dump_yaml(compose_test_1, "test-compose-1.yaml") dump_yaml(compose_test_1, "test-compose-1.yaml")
dump_yaml(compose_test_2, "test-compose-2.yaml") dump_yaml(compose_test_2, "test-compose-2.yaml")
@ -135,15 +113,7 @@ def test__parse_compose_file_when_multiple_composes() -> None:
if podman_compose.services: if podman_compose.services:
podman_compose.services["test-service"].pop("_deps") podman_compose.services["test-service"].pop("_deps")
actual_compose = podman_compose.services["test-service"] actual_compose = podman_compose.services["test-service"]
if actual_compose != expected_result: self.assertEqual(actual_compose, expected)
print("compose: ", test_input)
print("override: ", test_override)
print("expected: ", expected_result)
print("actual: ", actual_compose)
compose_expected = expected_result
assert compose_expected == actual_compose
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None: def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:

View File

@ -2,11 +2,15 @@ import copy
import os import os
import argparse import argparse
import yaml import yaml
import unittest
from parameterized import parameterized
from podman_compose import normalize_service, PodmanCompose from podman_compose import normalize_service, PodmanCompose
test_keys = ["command", "entrypoint"] test_keys = ["command", "entrypoint"]
test_cases_normalise_pre_merge = [
class TestMergeBuild(unittest.TestCase):
@parameterized.expand([
({"$$$": []}, {"$$$": []}), ({"$$$": []}, {"$$$": []}),
({"$$$": ["sh"]}, {"$$$": ["sh"]}), ({"$$$": ["sh"]}, {"$$$": ["sh"]}),
({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}), ({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}),
@ -16,9 +20,15 @@ test_cases_normalise_pre_merge = [
{"$$$": "bash -c 'sleep infinity'"}, {"$$$": "bash -c 'sleep infinity'"},
{"$$$": ["bash", "-c", "sleep infinity"]}, {"$$$": ["bash", "-c", "sleep infinity"]},
), ),
] ])
def test_normalize_service(self, input_template, expected_template):
for key in test_keys:
test_input, _, expected = template_to_expression(
input_template, {}, expected_template, key
)
self.assertEqual(normalize_service(test_input), expected)
test_cases_merges = [ @parameterized.expand([
({}, {"$$$": []}, {"$$$": []}), ({}, {"$$$": []}, {"$$$": []}),
({"$$$": []}, {}, {"$$$": []}), ({"$$$": []}, {}, {"$$$": []}),
({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), ({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
@ -39,39 +49,10 @@ test_cases_merges = [
{"$$$": "bash -c 'sleep infinity'"}, {"$$$": "bash -c 'sleep infinity'"},
{"$$$": ["bash", "-c", "sleep infinity"]}, {"$$$": ["bash", "-c", "sleep infinity"]},
), ),
] ])
def test_parse_compose_file_when_multiple_composes(
self, base_template, override_template, expected_template
def template_to_expression(base, override, expected, key): ):
base_copy = copy.deepcopy(base)
override_copy = copy.deepcopy(override)
expected_copy = copy.deepcopy(expected)
expected_copy[key] = expected_copy.pop("$$$")
if "$$$" in base:
base_copy[key] = base_copy.pop("$$$")
if "$$$" in override:
override_copy[key] = override_copy.pop("$$$")
return base_copy, override_copy, expected_copy
def test_normalize_service():
for test_input_template, expected_template in test_cases_normalise_pre_merge:
for key in test_keys:
test_input, _, expected = template_to_expression(
test_input_template, {}, expected_template, key
)
test_input = normalize_service(test_input)
test_result = expected == test_input
if not test_result:
print("base_template: ", test_input_template)
print("expected: ", expected)
print("actual: ", test_input)
assert test_result
def test__parse_compose_file_when_multiple_composes() -> None:
for base_template, override_template, expected_template in copy.deepcopy(test_cases_merges):
for key in test_keys: for key in test_keys:
base, override, expected = template_to_expression( base, override, expected = template_to_expression(
base_template, override_template, expected_template, key base_template, override_template, expected_template, key
@ -90,12 +71,20 @@ def test__parse_compose_file_when_multiple_composes() -> None:
if podman_compose.services: if podman_compose.services:
podman_compose.services["test-service"].pop("_deps") podman_compose.services["test-service"].pop("_deps")
actual = podman_compose.services["test-service"] actual = podman_compose.services["test-service"]
if actual != expected: self.assertEqual(actual, expected)
print("compose: ", base)
print("override: ", override)
print("result: ", expected)
assert expected == actual
def template_to_expression(base, override, expected, key):
base_copy = copy.deepcopy(base)
override_copy = copy.deepcopy(override)
expected_copy = copy.deepcopy(expected)
expected_copy[key] = expected_copy.pop("$$$")
if "$$$" in base:
base_copy[key] = base_copy.pop("$$$")
if "$$$" in override:
override_copy[key] = override_copy.pop("$$$")
return base_copy, override_copy, expected_copy
def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None: def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None:

View File

@ -4,16 +4,19 @@ import argparse
import copy import copy
import os import os
import yaml import yaml
import unittest
from parameterized import parameterized
from podman_compose import ( from podman_compose import (
normalize_service,
normalize,
normalize_final, normalize_final,
normalize_service_final, normalize_service_final,
PodmanCompose, PodmanCompose,
) )
cwd = os.path.abspath(".") cwd = os.path.abspath(".")
test_cases_simple_normalization = [
class TestNormalizeFullBuild(unittest.TestCase):
cases_simple_normalization = [
({"image": "test-image"}, {"image": "test-image"}), ({"image": "test-image"}, {"image": "test-image"}),
( (
{"build": "."}, {"build": "."},
@ -98,32 +101,22 @@ test_cases_simple_normalization = [
), ),
] ]
@parameterized.expand(cases_simple_normalization)
# def test_normalize_service_final_returns_absolute_path_in_context(self, input, expected):
# [service.build] is normalised after merges # Tests that [service.build] is normalized after merges
#
def test_normalize_service_final_returns_absolute_path_in_context() -> None:
project_dir = cwd project_dir = cwd
for test_input, expected_service in copy.deepcopy(test_cases_simple_normalization): self.assertEqual(normalize_service_final(input, project_dir), expected)
actual_service = normalize_service_final(test_input, project_dir)
assert expected_service == actual_service
@parameterized.expand(cases_simple_normalization)
def test_normalize_returns_absolute_path_in_context() -> None: def test_normalize_returns_absolute_path_in_context(self, input, expected):
project_dir = cwd project_dir = cwd
for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization): compose_test = {"services": {"test-service": input}}
compose_test = {"services": {"test-service": test_input}} compose_expected = {"services": {"test-service": expected}}
compose_expected = {"services": {"test-service": expected_result}} self.assertEqual(normalize_final(compose_test, project_dir), compose_expected)
actual_compose = normalize_final(compose_test, project_dir)
assert compose_expected == actual_compose
@parameterized.expand(cases_simple_normalization)
# def test_parse_compose_file_when_single_compose(self, input, expected):
# running full parse over single compose files compose_test = {"services": {"test-service": input}}
#
def test__parse_compose_file_when_single_compose() -> None:
for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization):
compose_test = {"services": {"test-service": test_input}}
dump_yaml(compose_test, "test-compose.yaml") dump_yaml(compose_test, "test-compose.yaml")
podman_compose = PodmanCompose() podman_compose = PodmanCompose()
@ -135,14 +128,9 @@ def test__parse_compose_file_when_single_compose() -> None:
if podman_compose.services: if podman_compose.services:
podman_compose.services["test-service"].pop("_deps") podman_compose.services["test-service"].pop("_deps")
actual_compose = podman_compose.services["test-service"] actual_compose = podman_compose.services["test-service"]
if actual_compose != expected_result: self.assertEqual(actual_compose, expected)
print("compose: ", test_input)
print("result: ", expected_result)
assert expected_result == actual_compose @parameterized.expand([
test_cases_with_merges = [
( (
{}, {},
{"build": "."}, {"build": "."},
@ -236,16 +224,10 @@ test_cases_with_merges = [
} }
}, },
), ),
] ])
def test_parse_when_multiple_composes(self, input, override, expected):
compose_test_1 = {"services": {"test-service": input}}
# compose_test_2 = {"services": {"test-service": override}}
# running full parse over merged
#
def test__parse_compose_file_when_multiple_composes() -> None:
for test_input, test_override, expected_result in copy.deepcopy(test_cases_with_merges):
compose_test_1 = {"services": {"test-service": test_input}}
compose_test_2 = {"services": {"test-service": test_override}}
dump_yaml(compose_test_1, "test-compose-1.yaml") dump_yaml(compose_test_1, "test-compose-1.yaml")
dump_yaml(compose_test_2, "test-compose-2.yaml") dump_yaml(compose_test_2, "test-compose-2.yaml")
@ -262,13 +244,7 @@ def test__parse_compose_file_when_multiple_composes() -> None:
if podman_compose.services: if podman_compose.services:
podman_compose.services["test-service"].pop("_deps") podman_compose.services["test-service"].pop("_deps")
actual_compose = podman_compose.services["test-service"] actual_compose = podman_compose.services["test-service"]
if actual_compose != expected_result: self.assertEqual(actual_compose, expected)
print("compose: ", test_input)
print("override: ", test_override)
print("result: ", expected_result)
compose_expected = expected_result
assert compose_expected == actual_compose
def set_args(podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool) -> None: def set_args(podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool) -> None:

View File

@ -1,21 +1,19 @@
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
import pytest import unittest
from podman_compose import parse_short_mount from podman_compose import parse_short_mount
@pytest.fixture class ParseShortMountTests(unittest.TestCase):
def multi_propagation_mount_str(): def test_multi_propagation(self):
return "/foo/bar:/baz:U,Z" self.assertEqual(
parse_short_mount("/foo/bar:/baz:U,Z", "/"),
{
def test_parse_short_mount_multi_propagation(multi_propagation_mount_str):
expected = {
"type": "bind", "type": "bind",
"source": "/foo/bar", "source": "/foo/bar",
"target": "/baz", "target": "/baz",
"bind": { "bind": {
"propagation": "U,Z", "propagation": "U,Z",
}, },
} },
assert parse_short_mount(multi_propagation_mount_str, "/") == expected )

View File

@ -37,12 +37,10 @@ setup(
"pyyaml", "pyyaml",
"python-dotenv", "python-dotenv",
], ],
extras_require={"devel": ["ruff", "pre-commit", "coverage"]}, extras_require={"devel": ["ruff", "pre-commit", "coverage", "parameterize"]},
# test_suite='tests', # test_suite='tests',
# tests_require=[ # tests_require=[
# 'coverage', # 'coverage',
# 'pytest-cov',
# 'pytest',
# 'tox', # 'tox',
# ] # ]
) )

View File

@ -1,5 +1,6 @@
-e . -e .
coverage==7.4.3 coverage==7.4.3
parameterized==0.9.0
pytest==8.0.2 pytest==8.0.2
tox==4.13.0 tox==4.13.0
ruff==0.3.1 ruff==0.3.1

0
tests/__init__.py Normal file
View File

View File

@ -1,27 +0,0 @@
"""conftest.py
Defines global pytest fixtures available to all tests.
"""
# pylint: disable=redefined-outer-name
from pathlib import Path
import os
import pytest
@pytest.fixture
def base_path():
"""Returns the base path for the project"""
return Path(__file__).parent.parent
@pytest.fixture
def test_path(base_path):
"""Returns the path to the tests directory"""
return os.path.join(base_path, "tests")
@pytest.fixture
def podman_compose_path(base_path):
"""Returns the path to the podman compose script"""
return os.path.join(base_path, "podman_compose.py")

View File

@ -1,8 +1,10 @@
from pathlib import Path from pathlib import Path
import subprocess import subprocess
import os
import unittest
def capture(command): def run_subprocess(command):
proc = subprocess.Popen( proc = subprocess.Popen(
command, command,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -12,7 +14,23 @@ def capture(command):
return out, err, proc.returncode return out, err, proc.returncode
def test_podman_compose_extends_w_file_subdir(): def base_path():
"""Returns the base path for the project"""
return Path(__file__).parent.parent
def test_path():
"""Returns the path to the tests directory"""
return os.path.join(base_path(), "tests")
def podman_compose_path():
"""Returns the path to the podman compose script"""
return os.path.join(base_path(), "podman_compose.py")
class TestPodmanCompose(unittest.TestCase):
def test_extends_w_file_subdir(self):
""" """
Test that podman-compose can execute podman-compose -f <file> up with extended File which Test that podman-compose can execute podman-compose -f <file> up with extended File which
includes a build context includes a build context
@ -49,23 +67,21 @@ def test_podman_compose_extends_w_file_subdir():
"docker.io/library/busybox", "docker.io/library/busybox",
] ]
out, _, returncode = capture(command_up) out, _, returncode = run_subprocess(command_up)
assert 0 == returncode self.assertEqual(returncode, 0)
# check container was created and exists # check container was created and exists
out, err, returncode = capture(command_check_container) out, err, returncode = run_subprocess(command_check_container)
assert 0 == returncode self.assertEqual(returncode, 0)
assert b'localhost/subdir_test:me\n' == out self.assertEqual(out, b'localhost/subdir_test:me\n')
out, _, returncode = capture(command_down) out, _, returncode = run_subprocess(command_down)
# cleanup test image(tags) # cleanup test image(tags)
assert 0 == returncode self.assertEqual(returncode, 0)
print('ok')
# check container did not exists anymore # check container did not exists anymore
out, _, returncode = capture(command_check_container) out, _, returncode = run_subprocess(command_check_container)
assert 0 == returncode self.assertEqual(returncode, 0)
assert b'' == out self.assertEqual(out, b'')
def test_extends_w_empty_service(self):
def test_podman_compose_extends_w_empty_service():
""" """
Test that podman-compose can execute podman-compose -f <file> up with extended File which Test that podman-compose can execute podman-compose -f <file> up with extended File which
includes an empty service. (e.g. if the file is used as placeholder for more complex configurations.) includes an empty service. (e.g. if the file is used as placeholder for more complex configurations.)
@ -82,5 +98,5 @@ def test_podman_compose_extends_w_empty_service():
"-d", "-d",
] ]
_, _, returncode = capture(command_up) _, _, returncode = run_subprocess(command_up)
assert 0 == returncode self.assertEqual(returncode, 0)

View File

@ -6,36 +6,41 @@ Tests the podman-compose config command which is used to return defined compose
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
import os import os
from test_podman_compose import capture from .test_podman_compose import podman_compose_path
import pytest from .test_podman_compose import run_subprocess
from .test_podman_compose import test_path
import unittest
from parameterized import parameterized
@pytest.fixture def profile_compose_file():
def profile_compose_file(test_path):
""" "Returns the path to the `profile` compose file used for this test module""" """ "Returns the path to the `profile` compose file used for this test module"""
return os.path.join(test_path, "profile", "docker-compose.yml") return os.path.join(test_path(), "profile", "docker-compose.yml")
def test_config_no_profiles(podman_compose_path, profile_compose_file): class TestComposeConfig(unittest.TestCase):
def test_config_no_profiles(self):
""" """
Tests podman-compose config command without profile enablement. Tests podman-compose config command without profile enablement.
:param podman_compose_path: The fixture used to specify the path to the podman compose file.
:param profile_compose_file: The fixtued used to specify the path to the "profile" compose used in the test.
""" """
config_cmd = ["coverage", "run", podman_compose_path, "-f", profile_compose_file, "config"] config_cmd = [
"coverage",
"run",
podman_compose_path(),
"-f",
profile_compose_file(),
"config",
]
out, _, return_code = capture(config_cmd) out, _, return_code = run_subprocess(config_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
string_output = out.decode("utf-8") string_output = out.decode("utf-8")
assert "default-service" in string_output self.assertIn("default-service", string_output)
assert "service-1" not in string_output self.assertNotIn("service-1", string_output)
assert "service-2" not in string_output self.assertNotIn("service-2", string_output)
@parameterized.expand(
@pytest.mark.parametrize(
"profiles, expected_services",
[ [
( (
["--profile", "profile-1", "config"], ["--profile", "profile-1", "config"],
@ -51,27 +56,25 @@ def test_config_no_profiles(podman_compose_path, profile_compose_file):
), ),
], ],
) )
def test_config_profiles(podman_compose_path, profile_compose_file, profiles, expected_services): def test_config_profiles(self, profiles, expected_services):
""" """
Tests podman-compose Tests podman-compose
:param podman_compose_path: The fixture used to specify the path to the podman compose file.
:param profile_compose_file: The fixtued used to specify the path to the "profile" compose used in the test.
:param profiles: The enabled profiles for the parameterized test. :param profiles: The enabled profiles for the parameterized test.
:param expected_services: Dictionary used to model the expected "enabled" services in the profile. :param expected_services: Dictionary used to model the expected "enabled" services in the profile.
Key = service name, Value = True if the service is enabled, otherwise False. Key = service name, Value = True if the service is enabled, otherwise False.
""" """
config_cmd = ["coverage", "run", podman_compose_path, "-f", profile_compose_file] config_cmd = ["coverage", "run", podman_compose_path(), "-f", profile_compose_file()]
config_cmd.extend(profiles) config_cmd.extend(profiles)
out, _, return_code = capture(config_cmd) out, _, return_code = run_subprocess(config_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
actual_output = out.decode("utf-8") actual_output = out.decode("utf-8")
assert len(expected_services) == 3 self.assertEqual(len(expected_services), 3)
actual_services = {} actual_services = {}
for service, _ in expected_services.items(): for service, _ in expected_services.items():
actual_services[service] = service in actual_output actual_services[service] = service in actual_output
assert expected_services == actual_services self.assertEqual(expected_services, actual_services)

View File

@ -1,8 +1,9 @@
from pathlib import Path from pathlib import Path
import subprocess import subprocess
import unittest
def capture(command): def run_subprocess(command):
proc = subprocess.Popen( proc = subprocess.Popen(
command, command,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
@ -12,7 +13,8 @@ def capture(command):
return out, err, proc.returncode return out, err, proc.returncode
def test_podman_compose_include(): class TestPodmanComposeInclude(unittest.TestCase):
def test_podman_compose_include(self):
""" """
Test that podman-compose can execute podman-compose -f <file> up with include Test that podman-compose can execute podman-compose -f <file> up with include
:return: :return:
@ -51,22 +53,22 @@ def test_podman_compose_include():
command_down = ["podman", "rm", "--force", "CONTAINER_ID"] command_down = ["podman", "rm", "--force", "CONTAINER_ID"]
out, _, returncode = capture(command_up) out, _, returncode = run_subprocess(command_up)
assert 0 == returncode self.assertEqual(returncode, 0)
out, _, returncode = capture(command_check_container) out, _, returncode = run_subprocess(command_check_container)
assert 0 == returncode self.assertEqual(returncode, 0)
assert out == b'"docker.io/library/busybox:latest"\n' self.assertEqual(out, b'"docker.io/library/busybox:latest"\n')
# Get container ID to remove it # Get container ID to remove it
out, _, returncode = capture(command_container_id) out, _, returncode = run_subprocess(command_container_id)
assert 0 == returncode self.assertEqual(returncode, 0)
assert out != b"" self.assertNotEqual(out, b"")
container_id = out.decode().strip().replace('"', "") container_id = out.decode().strip().replace('"', "")
command_down[3] = container_id command_down[3] = container_id
out, _, returncode = capture(command_down) out, _, returncode = run_subprocess(command_down)
# cleanup test image(tags) # cleanup test image(tags)
assert 0 == returncode self.assertEqual(returncode, 0)
assert out != b"" self.assertNotEqual(out, b"")
# check container did not exists anymore # check container did not exists anymore
out, _, returncode = capture(command_check_container) out, _, returncode = run_subprocess(command_check_container)
assert 0 == returncode self.assertEqual(returncode, 0)
assert out == b"" self.assertEqual(out, b"")

View File

@ -7,37 +7,40 @@ Tests the podman compose up and down commands used to create and remove services
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
import os import os
import time import time
import unittest
from test_podman_compose import capture from .test_podman_compose import run_subprocess
from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
def test_exit_from(podman_compose_path, test_path): class TestPodmanCompose(unittest.TestCase):
def test_exit_from(self):
up_cmd = [ up_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "exit-from", "docker-compose.yaml"), os.path.join(test_path(), "exit-from", "docker-compose.yaml"),
"up", "up",
] ]
out, _, return_code = capture(up_cmd + ["--exit-code-from", "sh1"]) out, _, return_code = run_subprocess(up_cmd + ["--exit-code-from", "sh1"])
assert return_code == 1 self.assertEqual(return_code, 1)
out, _, return_code = capture(up_cmd + ["--exit-code-from", "sh2"]) out, _, return_code = run_subprocess(up_cmd + ["--exit-code-from", "sh2"])
assert return_code == 2 self.assertEqual(return_code, 2)
def test_run(self):
def test_run(podman_compose_path, test_path):
""" """
This will test depends_on as well This will test depends_on as well
""" """
run_cmd = [ run_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "deps", "docker-compose.yaml"), os.path.join(test_path(), "deps", "docker-compose.yaml"),
"run", "run",
"--rm", "--rm",
"sleep", "sleep",
@ -46,17 +49,17 @@ def test_run(podman_compose_path, test_path):
"wget -q -O - http://web:8000/hosts", "wget -q -O - http://web:8000/hosts",
] ]
out, _, return_code = capture(run_cmd) out, _, return_code = run_subprocess(run_cmd)
assert b'127.0.0.1\tlocalhost' in out self.assertIn(b'127.0.0.1\tlocalhost', out)
# Run it again to make sure we can run it twice. I saw an issue where a second run, with the container left up, # Run it again to make sure we can run it twice. I saw an issue where a second run, with the container left up,
# would fail # would fail
run_cmd = [ run_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "deps", "docker-compose.yaml"), os.path.join(test_path(), "deps", "docker-compose.yaml"),
"run", "run",
"--rm", "--rm",
"sleep", "sleep",
@ -65,31 +68,30 @@ def test_run(podman_compose_path, test_path):
"wget -q -O - http://web:8000/hosts", "wget -q -O - http://web:8000/hosts",
] ]
out, _, return_code = capture(run_cmd) out, _, return_code = run_subprocess(run_cmd)
assert b'127.0.0.1\tlocalhost' in out assert b'127.0.0.1\tlocalhost' in out
assert return_code == 0 self.assertEqual(return_code, 0)
# This leaves a container running. Not sure it's intended, but it matches docker-compose # This leaves a container running. Not sure it's intended, but it matches docker-compose
down_cmd = [ down_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "deps", "docker-compose.yaml"), os.path.join(test_path(), "deps", "docker-compose.yaml"),
"down", "down",
] ]
out, _, return_code = capture(run_cmd) out, _, return_code = run_subprocess(run_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
def test_up_with_ports(self):
def test_up_with_ports(podman_compose_path, test_path):
up_cmd = [ up_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "ports", "docker-compose.yml"), os.path.join(test_path(), "ports", "docker-compose.yml"),
"up", "up",
"-d", "-d",
"--force-recreate", "--force-recreate",
@ -98,29 +100,28 @@ def test_up_with_ports(podman_compose_path, test_path):
down_cmd = [ down_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "ports", "docker-compose.yml"), os.path.join(test_path(), "ports", "docker-compose.yml"),
"down", "down",
"--volumes", "--volumes",
] ]
try: try:
out, _, return_code = capture(up_cmd) out, _, return_code = run_subprocess(up_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
finally: finally:
out, _, return_code = capture(down_cmd) out, _, return_code = run_subprocess(down_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
def test_down_with_vols(self):
def test_down_with_vols(podman_compose_path, test_path):
up_cmd = [ up_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "vol", "docker-compose.yaml"), os.path.join(test_path(), "vol", "docker-compose.yaml"),
"up", "up",
"-d", "-d",
] ]
@ -128,33 +129,37 @@ def test_down_with_vols(podman_compose_path, test_path):
down_cmd = [ down_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "vol", "docker-compose.yaml"), os.path.join(test_path(), "vol", "docker-compose.yaml"),
"down", "down",
"--volumes", "--volumes",
] ]
try: try:
out, _, return_code = capture(["podman", "volume", "create", "my-app-data"]) out, _, return_code = run_subprocess(["podman", "volume", "create", "my-app-data"])
assert return_code == 0 self.assertEqual(return_code, 0)
out, _, return_code = capture(["podman", "volume", "create", "actual-name-of-volume"]) out, _, return_code = run_subprocess([
assert return_code == 0 "podman",
"volume",
"create",
"actual-name-of-volume",
])
self.assertEqual(return_code, 0)
out, _, return_code = capture(up_cmd) out, _, return_code = run_subprocess(up_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
capture(["podman", "inspect", "volume", ""]) run_subprocess(["podman", "inspect", "volume", ""])
finally: finally:
out, _, return_code = capture(down_cmd) out, _, return_code = run_subprocess(down_cmd)
capture(["podman", "volume", "rm", "my-app-data"]) run_subprocess(["podman", "volume", "rm", "my-app-data"])
capture(["podman", "volume", "rm", "actual-name-of-volume"]) run_subprocess(["podman", "volume", "rm", "actual-name-of-volume"])
assert return_code == 0 self.assertEqual(return_code, 0)
def test_down_with_orphans(self):
def test_down_with_orphans(podman_compose_path, test_path): container_id, _, return_code = run_subprocess([
container_id, _, return_code = capture([
"podman", "podman",
"run", "run",
"--rm", "--rm",
@ -172,17 +177,22 @@ def test_down_with_orphans(podman_compose_path, test_path):
down_cmd = [ down_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
os.path.join(test_path, "ports", "docker-compose.yml"), os.path.join(test_path(), "ports", "docker-compose.yml"),
"down", "down",
"--volumes", "--volumes",
"--remove-orphans", "--remove-orphans",
] ]
out, _, return_code = capture(down_cmd) out, _, return_code = run_subprocess(down_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
_, _, exists = capture(["podman", "container", "exists", container_id.decode("utf-8")]) _, _, exists = run_subprocess([
"podman",
"container",
"exists",
container_id.decode("utf-8"),
])
assert exists == 1 self.assertEqual(exists, 1)

View File

@ -6,44 +6,40 @@ Tests the podman compose up and down commands used to create and remove services
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
import os import os
from test_podman_compose import capture from .test_podman_compose import run_subprocess
import pytest from .test_podman_compose import podman_compose_path
from .test_podman_compose import test_path
from parameterized import parameterized
import unittest
@pytest.fixture def profile_compose_file():
def profile_compose_file(test_path):
""" "Returns the path to the `profile` compose file used for this test module""" """ "Returns the path to the `profile` compose file used for this test module"""
return os.path.join(test_path, "profile", "docker-compose.yml") return os.path.join(test_path(), "profile", "docker-compose.yml")
@pytest.fixture(autouse=True) class TestUpDown(unittest.TestCase):
def teardown(podman_compose_path, profile_compose_file): def tearDown(self):
""" """
Ensures that the services within the "profile compose file" are removed between each test case. Ensures that the services within the "profile compose file" are removed between each test case.
:param podman_compose_path: The path to the podman compose script.
:param profile_compose_file: The path to the compose file used for this test module.
""" """
# run the test case # run the test case
yield
down_cmd = [ down_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"--profile", "--profile",
"profile-1", "profile-1",
"--profile", "--profile",
"profile-2", "profile-2",
"-f", "-f",
profile_compose_file, profile_compose_file(),
"down", "down",
] ]
capture(down_cmd) run_subprocess(down_cmd)
@parameterized.expand(
@pytest.mark.parametrize(
"profiles, expected_services",
[ [
( (
["--profile", "profile-1", "up", "-d"], ["--profile", "profile-1", "up", "-d"],
@ -59,18 +55,18 @@ def teardown(podman_compose_path, profile_compose_file):
), ),
], ],
) )
def test_up(podman_compose_path, profile_compose_file, profiles, expected_services): def test_up(self, profiles, expected_services):
up_cmd = [ up_cmd = [
"coverage", "coverage",
"run", "run",
podman_compose_path, podman_compose_path(),
"-f", "-f",
profile_compose_file, profile_compose_file(),
] ]
up_cmd.extend(profiles) up_cmd.extend(profiles)
out, _, return_code = capture(up_cmd) out, _, return_code = run_subprocess(up_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
check_cmd = [ check_cmd = [
"podman", "podman",
@ -79,14 +75,14 @@ def test_up(podman_compose_path, profile_compose_file, profiles, expected_servic
"--format", "--format",
'"{{.Names}}"', '"{{.Names}}"',
] ]
out, _, return_code = capture(check_cmd) out, _, return_code = run_subprocess(check_cmd)
assert return_code == 0 self.assertEqual(return_code, 0)
assert len(expected_services) == 3 self.assertEqual(len(expected_services), 3)
actual_output = out.decode("utf-8") actual_output = out.decode("utf-8")
actual_services = {} actual_services = {}
for service, _ in expected_services.items(): for service, _ in expected_services.items():
actual_services[service] = service in actual_output actual_services[service] = service in actual_output
assert expected_services == actual_services self.assertEqual(expected_services, actual_services)