diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 5997d97..399051f 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,15 +7,12 @@ on: jobs: lint-ruff: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.11"] steps: - uses: actions/checkout@v3 - - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.10 + uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: "3.11" - name: Analysing the code with ruff run: | pip install -r test-requirements.txt diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 2f7cf41..78d3f3b 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -25,15 +25,9 @@ jobs: python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r 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: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # 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 run --source podman_compose -m unittest pytests/*.py + python -m unittest tests/*.py coverage combine coverage report diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3aca322..c4ca8e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,8 +41,8 @@ $ pre-commit run --all-files ``` 6. Run code coverage ```shell -coverage run --source podman_compose -m pytest ./pytests -python -m pytest ./tests +coverage run --source podman_compose -m unittest pytests/*.py +python -m unittest tests/*.py coverage combine coverage report coverage html diff --git a/README.md b/README.md index f7e67a6..a6927f2 100644 --- a/README.md +++ b/README.md @@ -103,11 +103,11 @@ There is also AWX 17.1.0 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 -### Unit tests with pytest -run a pytest with following command +### Unit tests with unittest +run a unittest with following command ```shell -python -m pytest pytests +python -m unittest pytests/*.py ``` # Contributing guide diff --git a/pytests/__init__.py b/pytests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pytests/test_can_merge_build.py b/pytests/test_can_merge_build.py index d08081a..600dc42 100644 --- a/pytests/test_can_merge_build.py +++ b/pytests/test_can_merge_build.py @@ -2,127 +2,105 @@ import os import argparse import yaml +import unittest +from parameterized import parameterized from podman_compose import normalize_service, PodmanCompose -test_cases_simple = [ - ({"test": "test"}, {"test": "test"}), - ({"build": "."}, {"build": {"context": "."}}), - ({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}), - ({"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), - ( - {"build": {"dockerfile": "dockerfile-1"}}, - {"build": {"dockerfile": "dockerfile-1"}}, - ), - ( - {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, - {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, - ), -] - - -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"}), - ({"build": "."}, {"build": {"context": "./sub_dir/."}}), - ({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}), - ({"build": {"context": "./dir-1"}}, {"build": {"context": "./sub_dir/dir-1"}}), - ( - {"build": {"dockerfile": "dockerfile-1"}}, - {"build": {"context": "./sub_dir", "dockerfile": "dockerfile-1"}}, - ), - ( - {"build": {"context": "./dir-1", "dockerfile": "dockerfile-1"}}, - {"build": {"context": "./sub_dir/dir-1", "dockerfile": "dockerfile-1"}}, - ), -] - - -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-1"}, {"test": "test-2"}, {"test": "test-2"}), - ({}, {"build": "."}, {"build": {"context": "."}}), - ({"build": "."}, {}, {"build": {"context": "."}}), - ({"build": "./dir-1"}, {"build": "./dir-2"}, {"build": {"context": "./dir-2"}}), - ({}, {"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), - ({"build": {"context": "./dir-1"}}, {}, {"build": {"context": "./dir-1"}}), - ( - {"build": {"context": "./dir-1"}}, - {"build": {"context": "./dir-2"}}, - {"build": {"context": "./dir-2"}}, - ), - ( - {}, - {"build": {"dockerfile": "dockerfile-1"}}, - {"build": {"dockerfile": "dockerfile-1"}}, - ), - ( - {"build": {"dockerfile": "dockerfile-1"}}, - {}, - {"build": {"dockerfile": "dockerfile-1"}}, - ), - ( - {"build": {"dockerfile": "./dockerfile-1"}}, - {"build": {"dockerfile": "./dockerfile-2"}}, - {"build": {"dockerfile": "./dockerfile-2"}}, - ), - ( - {"build": {"dockerfile": "./dockerfile-1"}}, - {"build": {"context": "./dir-2"}}, - {"build": {"dockerfile": "./dockerfile-1", "context": "./dir-2"}}, - ), - ( - {"build": {"dockerfile": "./dockerfile-1", "context": "./dir-1"}}, - {"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}}, - {"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}}, - ), - ( - {"build": {"dockerfile": "./dockerfile-1"}}, - {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, - ), - ( - {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./dockerfile-1"}}, - {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1"]}}, - ), - ( - {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV2=2"]}}, - {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1", "ENV2=2"]}}, - ), -] - - -def test__parse_compose_file_when_multiple_composes() -> None: - 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}} +class TestMergeBuild(unittest.TestCase): + @parameterized.expand([ + ({"test": "test"}, {"test": "test"}), + ({"build": "."}, {"build": {"context": "."}}), + ({"build": "./dir-1"}, {"build": {"context": "./dir-1"}}), + ({"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), + ( + {"build": {"dockerfile": "dockerfile-1"}}, + {"build": {"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([ + ({"test": "test"}, {"test": "test"}), + ({"build": "."}, {"build": {"context": "./sub_dir/."}}), + ({"build": "./dir-1"}, {"build": {"context": "./sub_dir/dir-1"}}), + ({"build": {"context": "./dir-1"}}, {"build": {"context": "./sub_dir/dir-1"}}), + ( + {"build": {"dockerfile": "dockerfile-1"}}, + {"build": {"context": "./sub_dir", "dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"context": "./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([ + ({}, {}, {}), + ({}, {"test": "test"}, {"test": "test"}), + ({"test": "test"}, {}, {"test": "test"}), + ({"test": "test-1"}, {"test": "test-2"}, {"test": "test-2"}), + ({}, {"build": "."}, {"build": {"context": "."}}), + ({"build": "."}, {}, {"build": {"context": "."}}), + ({"build": "./dir-1"}, {"build": "./dir-2"}, {"build": {"context": "./dir-2"}}), + ({}, {"build": {"context": "./dir-1"}}, {"build": {"context": "./dir-1"}}), + ({"build": {"context": "./dir-1"}}, {}, {"build": {"context": "./dir-1"}}), + ( + {"build": {"context": "./dir-1"}}, + {"build": {"context": "./dir-2"}}, + {"build": {"context": "./dir-2"}}, + ), + ( + {}, + {"build": {"dockerfile": "dockerfile-1"}}, + {"build": {"dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"dockerfile": "dockerfile-1"}}, + {}, + {"build": {"dockerfile": "dockerfile-1"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"dockerfile": "./dockerfile-2"}}, + {"build": {"dockerfile": "./dockerfile-2"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"context": "./dir-2"}}, + {"build": {"dockerfile": "./dockerfile-1", "context": "./dir-2"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1", "context": "./dir-1"}}, + {"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}}, + {"build": {"dockerfile": "./dockerfile-2", "context": "./dir-2"}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./dockerfile-1"}}, + {"build": {"dockerfile": "./dockerfile-1", "args": ["ENV1=1"]}}, + ), + ( + {"build": {"dockerfile": "./dockerfile-2", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./dockerfile-1", "args": ["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}} + compose_test_2 = {"services": {"test-service": override}} dump_yaml(compose_test_1, "test-compose-1.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: podman_compose.services["test-service"].pop("_deps") actual_compose = podman_compose.services["test-service"] - if actual_compose != expected_result: - 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 + self.assertEqual(actual_compose, expected) def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None: diff --git a/pytests/test_can_merge_cmd_ent.py b/pytests/test_can_merge_cmd_ent.py index 1e5133f..6080945 100644 --- a/pytests/test_can_merge_cmd_ent.py +++ b/pytests/test_can_merge_cmd_ent.py @@ -2,76 +2,57 @@ import os import argparse import yaml +import unittest +from parameterized import parameterized from podman_compose import normalize_service, PodmanCompose test_keys = ["command", "entrypoint"] -test_cases_normalise_pre_merge = [ - ({"$$$": []}, {"$$$": []}), - ({"$$$": ["sh"]}, {"$$$": ["sh"]}), - ({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}), - ({"$$$": "sh"}, {"$$$": ["sh"]}), - ({"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}), - ( - {"$$$": "bash -c 'sleep infinity'"}, - {"$$$": ["bash", "-c", "sleep infinity"]}, - ), -] - -test_cases_merges = [ - ({}, {"$$$": []}, {"$$$": []}), - ({"$$$": []}, {}, {"$$$": []}), - ({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), - ({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}), - ({}, {"$$$": "sh"}, {"$$$": ["sh"]}), - ({"$$$": "sh"}, {}, {"$$$": ["sh"]}), - ({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), - ({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), - ({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}), - ({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), - ({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), - ({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}), - ({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}), - ({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}), - ({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}), - ( - {}, - {"$$$": "bash -c 'sleep infinity'"}, - {"$$$": ["bash", "-c", "sleep infinity"]}, - ), -] - -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: +class TestMergeBuild(unittest.TestCase): + @parameterized.expand([ + ({"$$$": []}, {"$$$": []}), + ({"$$$": ["sh"]}, {"$$$": ["sh"]}), + ({"$$$": ["sh", "-c", "date"]}, {"$$$": ["sh", "-c", "date"]}), + ({"$$$": "sh"}, {"$$$": ["sh"]}), + ({"$$$": "sleep infinity"}, {"$$$": ["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( - test_input_template, {}, expected_template, key + 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): + self.assertEqual(normalize_service(test_input), expected) + + @parameterized.expand([ + ({}, {"$$$": []}, {"$$$": []}), + ({"$$$": []}, {}, {"$$$": []}), + ({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}), + ({}, {"$$$": "sh"}, {"$$$": ["sh"]}), + ({"$$$": "sh"}, {}, {"$$$": ["sh"]}), + ({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), + ({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}), + ({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}), + ({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}), + ({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}), + ({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["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 + ): for key in test_keys: base, override, expected = template_to_expression( base_template, override_template, expected_template, key @@ -90,12 +71,20 @@ def test__parse_compose_file_when_multiple_composes() -> None: if podman_compose.services: podman_compose.services["test-service"].pop("_deps") actual = podman_compose.services["test-service"] - if actual != expected: - print("compose: ", base) - print("override: ", override) - print("result: ", expected) + self.assertEqual(actual, expected) + + +def template_to_expression(base, override, expected, key): + base_copy = copy.deepcopy(base) + override_copy = copy.deepcopy(override) + expected_copy = copy.deepcopy(expected) - assert expected == actual + 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: diff --git a/pytests/test_normalize_final_build.py b/pytests/test_normalize_final_build.py index 89477bf..4f0db34 100644 --- a/pytests/test_normalize_final_build.py +++ b/pytests/test_normalize_final_build.py @@ -4,126 +4,119 @@ import copy import os import yaml +import unittest +from parameterized import parameterized from podman_compose import ( - normalize_service, - normalize, normalize_final, normalize_service_final, PodmanCompose, ) cwd = os.path.abspath(".") -test_cases_simple_normalization = [ - ({"image": "test-image"}, {"image": "test-image"}), - ( - {"build": "."}, - { - "build": {"context": cwd, "dockerfile": "Dockerfile"}, - }, - ), - ( - {"build": "../relative"}, - { - "build": { - "context": os.path.normpath(os.path.join(cwd, "../relative")), - "dockerfile": "Dockerfile", + + +class TestNormalizeFullBuild(unittest.TestCase): + cases_simple_normalization = [ + ({"image": "test-image"}, {"image": "test-image"}), + ( + {"build": "."}, + { + "build": {"context": cwd, "dockerfile": "Dockerfile"}, }, - }, - ), - ( - {"build": "./relative"}, - { - "build": { - "context": os.path.normpath(os.path.join(cwd, "./relative")), - "dockerfile": "Dockerfile", + ), + ( + {"build": "../relative"}, + { + "build": { + "context": os.path.normpath(os.path.join(cwd, "../relative")), + "dockerfile": "Dockerfile", + }, }, - }, - ), - ( - {"build": "/workspace/absolute"}, - { - "build": { - "context": "/workspace/absolute", - "dockerfile": "Dockerfile", + ), + ( + {"build": "./relative"}, + { + "build": { + "context": os.path.normpath(os.path.join(cwd, "./relative")), + "dockerfile": "Dockerfile", + }, }, - }, - ), - ( - { - "build": { - "dockerfile": "Dockerfile", + ), + ( + {"build": "/workspace/absolute"}, + { + "build": { + "context": "/workspace/absolute", + "dockerfile": "Dockerfile", + }, }, - }, - { - "build": { - "context": cwd, - "dockerfile": "Dockerfile", + ), + ( + { + "build": { + "dockerfile": "Dockerfile", + }, }, - }, - ), - ( - { - "build": { - "context": ".", + { + "build": { + "context": cwd, + "dockerfile": "Dockerfile", + }, }, - }, - { - "build": { - "context": cwd, - "dockerfile": "Dockerfile", + ), + ( + { + "build": { + "context": ".", + }, }, - }, - ), - ( - { - "build": {"context": "../", "dockerfile": "test-dockerfile"}, - }, - { - "build": { - "context": os.path.normpath(os.path.join(cwd, "../")), - "dockerfile": "test-dockerfile", + { + "build": { + "context": cwd, + "dockerfile": "Dockerfile", + }, }, - }, - ), - ( - { - "build": {"context": ".", "dockerfile": "./dev/test-dockerfile"}, - }, - { - "build": { - "context": cwd, - "dockerfile": "./dev/test-dockerfile", + ), + ( + { + "build": {"context": "../", "dockerfile": "test-dockerfile"}, }, - }, - ), -] - - -# -# [service.build] is normalised after merges -# -def test_normalize_service_final_returns_absolute_path_in_context() -> None: - project_dir = cwd - for test_input, expected_service in copy.deepcopy(test_cases_simple_normalization): - actual_service = normalize_service_final(test_input, project_dir) - assert expected_service == actual_service - + { + "build": { + "context": os.path.normpath(os.path.join(cwd, "../")), + "dockerfile": "test-dockerfile", + }, + }, + ), + ( + { + "build": {"context": ".", "dockerfile": "./dev/test-dockerfile"}, + }, + { + "build": { + "context": cwd, + "dockerfile": "./dev/test-dockerfile", + }, + }, + ), + ] -def test_normalize_returns_absolute_path_in_context() -> None: - project_dir = cwd - for test_input, expected_result in copy.deepcopy(test_cases_simple_normalization): - compose_test = {"services": {"test-service": test_input}} - compose_expected = {"services": {"test-service": expected_result}} - actual_compose = normalize_final(compose_test, project_dir) - assert compose_expected == actual_compose + @parameterized.expand(cases_simple_normalization) + def test_normalize_service_final_returns_absolute_path_in_context(self, input, expected): + # Tests that [service.build] is normalized after merges + project_dir = cwd + self.assertEqual(normalize_service_final(input, project_dir), expected) + @parameterized.expand(cases_simple_normalization) + def test_normalize_returns_absolute_path_in_context(self, input, expected): + project_dir = cwd + compose_test = {"services": {"test-service": input}} + compose_expected = {"services": {"test-service": expected}} + self.assertEqual(normalize_final(compose_test, project_dir), compose_expected) -# -# running full parse over single compose files -# -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}} + @parameterized.expand(cases_simple_normalization) + def test_parse_compose_file_when_single_compose(self, input, expected): + compose_test = {"services": {"test-service": input}} dump_yaml(compose_test, "test-compose.yaml") podman_compose = PodmanCompose() @@ -135,117 +128,106 @@ def test__parse_compose_file_when_single_compose() -> None: if podman_compose.services: podman_compose.services["test-service"].pop("_deps") actual_compose = podman_compose.services["test-service"] - if actual_compose != expected_result: - print("compose: ", test_input) - print("result: ", expected_result) - - assert expected_result == actual_compose + self.assertEqual(actual_compose, expected) - -test_cases_with_merges = [ - ( - {}, - {"build": "."}, - {"build": {"context": cwd, "dockerfile": "Dockerfile"}}, - ), - ( - {"build": "."}, - {}, - {"build": {"context": cwd, "dockerfile": "Dockerfile"}}, - ), - ( - {"build": "/workspace/absolute"}, - {"build": "./relative"}, - { - "build": { - "context": os.path.normpath(os.path.join(cwd, "./relative")), - "dockerfile": "Dockerfile", - } - }, - ), - ( - {"build": "./relative"}, - {"build": "/workspace/absolute"}, - {"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}}, - ), - ( - {"build": "./relative"}, - {"build": "/workspace/absolute"}, - {"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}}, - ), - ( - {"build": {"dockerfile": "test-dockerfile"}}, - {}, - {"build": {"context": cwd, "dockerfile": "test-dockerfile"}}, - ), - ( - {}, - {"build": {"dockerfile": "test-dockerfile"}}, - {"build": {"context": cwd, "dockerfile": "test-dockerfile"}}, - ), - ( - {}, - {"build": {"dockerfile": "test-dockerfile"}}, - {"build": {"context": cwd, "dockerfile": "test-dockerfile"}}, - ), - ( - {"build": {"dockerfile": "test-dockerfile-1"}}, - {"build": {"dockerfile": "test-dockerfile-2"}}, - {"build": {"context": cwd, "dockerfile": "test-dockerfile-2"}}, - ), - ( - {"build": "/workspace/absolute"}, - {"build": {"dockerfile": "test-dockerfile"}}, - {"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}}, - ), - ( - {"build": {"dockerfile": "test-dockerfile"}}, - {"build": "/workspace/absolute"}, - {"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}}, - ), - ( - {"build": {"dockerfile": "./test-dockerfile-1"}}, - {"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV1=1"]}}, - { - "build": { - "context": cwd, - "dockerfile": "./test-dockerfile-2", - "args": ["ENV1=1"], - } - }, - ), - ( - {"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./test-dockerfile-2"}}, - { - "build": { - "context": cwd, - "dockerfile": "./test-dockerfile-2", - "args": ["ENV1=1"], - } - }, - ), - ( - {"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}}, - {"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV2=2"]}}, - { - "build": { - "context": cwd, - "dockerfile": "./test-dockerfile-2", - "args": ["ENV1=1", "ENV2=2"], - } - }, - ), -] - - -# -# 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}} + @parameterized.expand([ + ( + {}, + {"build": "."}, + {"build": {"context": cwd, "dockerfile": "Dockerfile"}}, + ), + ( + {"build": "."}, + {}, + {"build": {"context": cwd, "dockerfile": "Dockerfile"}}, + ), + ( + {"build": "/workspace/absolute"}, + {"build": "./relative"}, + { + "build": { + "context": os.path.normpath(os.path.join(cwd, "./relative")), + "dockerfile": "Dockerfile", + } + }, + ), + ( + {"build": "./relative"}, + {"build": "/workspace/absolute"}, + {"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}}, + ), + ( + {"build": "./relative"}, + {"build": "/workspace/absolute"}, + {"build": {"context": "/workspace/absolute", "dockerfile": "Dockerfile"}}, + ), + ( + {"build": {"dockerfile": "test-dockerfile"}}, + {}, + {"build": {"context": cwd, "dockerfile": "test-dockerfile"}}, + ), + ( + {}, + {"build": {"dockerfile": "test-dockerfile"}}, + {"build": {"context": cwd, "dockerfile": "test-dockerfile"}}, + ), + ( + {}, + {"build": {"dockerfile": "test-dockerfile"}}, + {"build": {"context": cwd, "dockerfile": "test-dockerfile"}}, + ), + ( + {"build": {"dockerfile": "test-dockerfile-1"}}, + {"build": {"dockerfile": "test-dockerfile-2"}}, + {"build": {"context": cwd, "dockerfile": "test-dockerfile-2"}}, + ), + ( + {"build": "/workspace/absolute"}, + {"build": {"dockerfile": "test-dockerfile"}}, + {"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}}, + ), + ( + {"build": {"dockerfile": "test-dockerfile"}}, + {"build": "/workspace/absolute"}, + {"build": {"context": "/workspace/absolute", "dockerfile": "test-dockerfile"}}, + ), + ( + {"build": {"dockerfile": "./test-dockerfile-1"}}, + {"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV1=1"]}}, + { + "build": { + "context": cwd, + "dockerfile": "./test-dockerfile-2", + "args": ["ENV1=1"], + } + }, + ), + ( + {"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./test-dockerfile-2"}}, + { + "build": { + "context": cwd, + "dockerfile": "./test-dockerfile-2", + "args": ["ENV1=1"], + } + }, + ), + ( + {"build": {"dockerfile": "./test-dockerfile-1", "args": ["ENV1=1"]}}, + {"build": {"dockerfile": "./test-dockerfile-2", "args": ["ENV2=2"]}}, + { + "build": { + "context": cwd, + "dockerfile": "./test-dockerfile-2", + "args": ["ENV1=1", "ENV2=2"], + } + }, + ), + ]) + def test_parse_when_multiple_composes(self, input, override, expected): + compose_test_1 = {"services": {"test-service": input}} + compose_test_2 = {"services": {"test-service": override}} dump_yaml(compose_test_1, "test-compose-1.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: podman_compose.services["test-service"].pop("_deps") actual_compose = podman_compose.services["test-service"] - if actual_compose != expected_result: - print("compose: ", test_input) - print("override: ", test_override) - print("result: ", expected_result) - compose_expected = expected_result - - assert compose_expected == actual_compose + self.assertEqual(actual_compose, expected) def set_args(podman_compose: PodmanCompose, file_names: list[str], no_normalize: bool) -> None: diff --git a/pytests/test_volumes.py b/pytests/test_volumes.py index 7f9d698..8107923 100644 --- a/pytests/test_volumes.py +++ b/pytests/test_volumes.py @@ -1,21 +1,19 @@ # pylint: disable=redefined-outer-name -import pytest +import unittest from podman_compose import parse_short_mount -@pytest.fixture -def multi_propagation_mount_str(): - return "/foo/bar:/baz:U,Z" - - -def test_parse_short_mount_multi_propagation(multi_propagation_mount_str): - expected = { - "type": "bind", - "source": "/foo/bar", - "target": "/baz", - "bind": { - "propagation": "U,Z", - }, - } - assert parse_short_mount(multi_propagation_mount_str, "/") == expected +class ParseShortMountTests(unittest.TestCase): + def test_multi_propagation(self): + self.assertEqual( + parse_short_mount("/foo/bar:/baz:U,Z", "/"), + { + "type": "bind", + "source": "/foo/bar", + "target": "/baz", + "bind": { + "propagation": "U,Z", + }, + }, + ) diff --git a/setup.py b/setup.py index 4664a87..f350a03 100644 --- a/setup.py +++ b/setup.py @@ -37,12 +37,10 @@ "pyyaml", "python-dotenv", ], - extras_require={"devel": ["ruff", "pre-commit", "coverage"]}, + extras_require={"devel": ["ruff", "pre-commit", "coverage", "parameterize"]}, # test_suite='tests', # tests_require=[ # 'coverage', - # 'pytest-cov', - # 'pytest', # 'tox', # ] ) diff --git a/test-requirements.txt b/test-requirements.txt index 51af4dc..a123291 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,6 @@ +-e . coverage==7.4.3 +parameterized==0.9.0 pytest==8.0.2 tox==4.13.0 ruff==0.3.1 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 3d5ffc3..0000000 --- a/tests/conftest.py +++ /dev/null @@ -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") diff --git a/tests/test_podman_compose.py b/tests/test_podman_compose.py index 84d730d..ed4dcac 100644 --- a/tests/test_podman_compose.py +++ b/tests/test_podman_compose.py @@ -1,8 +1,10 @@ from pathlib import Path import subprocess +import os +import unittest -def capture(command): +def run_subprocess(command): proc = subprocess.Popen( command, stdout=subprocess.PIPE, @@ -12,75 +14,89 @@ def capture(command): return out, err, proc.returncode -def test_podman_compose_extends_w_file_subdir(): - """ - Test that podman-compose can execute podman-compose -f up with extended File which - includes a build context - :return: - """ - main_path = Path(__file__).parent.parent - - command_up = [ - "coverage", - "run", - str(main_path.joinpath("podman_compose.py")), - "-f", - str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")), - "up", - "-d", - ] - - command_check_container = [ - "coverage", - "run", - str(main_path.joinpath("podman_compose.py")), - "-f", - str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")), - "ps", - "--format", - '{{.Image}}', - ] - - command_down = [ - "podman", - "rmi", - "--force", - "localhost/subdir_test:me", - "docker.io/library/busybox", - ] - - out, _, returncode = capture(command_up) - assert 0 == returncode - # check container was created and exists - out, err, returncode = capture(command_check_container) - assert 0 == returncode - assert b'localhost/subdir_test:me\n' == out - out, _, returncode = capture(command_down) - # cleanup test image(tags) - assert 0 == returncode - print('ok') - # check container did not exists anymore - out, _, returncode = capture(command_check_container) - assert 0 == returncode - assert b'' == out - - -def test_podman_compose_extends_w_empty_service(): - """ - Test that podman-compose can execute podman-compose -f up with extended File which - includes an empty service. (e.g. if the file is used as placeholder for more complex configurations.) - :return: - """ - main_path = Path(__file__).parent.parent - - command_up = [ - "python3", - str(main_path.joinpath("podman_compose.py")), - "-f", - str(main_path.joinpath("tests", "extends_w_empty_service", "docker-compose.yml")), - "up", - "-d", - ] - - _, _, returncode = capture(command_up) - assert 0 == returncode +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 up with extended File which + includes a build context + :return: + """ + main_path = Path(__file__).parent.parent + + command_up = [ + "coverage", + "run", + str(main_path.joinpath("podman_compose.py")), + "-f", + str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")), + "up", + "-d", + ] + + command_check_container = [ + "coverage", + "run", + str(main_path.joinpath("podman_compose.py")), + "-f", + str(main_path.joinpath("tests", "extends_w_file_subdir", "docker-compose.yml")), + "ps", + "--format", + '{{.Image}}', + ] + + command_down = [ + "podman", + "rmi", + "--force", + "localhost/subdir_test:me", + "docker.io/library/busybox", + ] + + out, _, returncode = run_subprocess(command_up) + self.assertEqual(returncode, 0) + # check container was created and exists + out, err, returncode = run_subprocess(command_check_container) + self.assertEqual(returncode, 0) + self.assertEqual(out, b'localhost/subdir_test:me\n') + out, _, returncode = run_subprocess(command_down) + # cleanup test image(tags) + self.assertEqual(returncode, 0) + # check container did not exists anymore + out, _, returncode = run_subprocess(command_check_container) + self.assertEqual(returncode, 0) + self.assertEqual(out, b'') + + def test_extends_w_empty_service(self): + """ + Test that podman-compose can execute podman-compose -f up with extended File which + includes an empty service. (e.g. if the file is used as placeholder for more complex configurations.) + :return: + """ + main_path = Path(__file__).parent.parent + + command_up = [ + "python3", + str(main_path.joinpath("podman_compose.py")), + "-f", + str(main_path.joinpath("tests", "extends_w_empty_service", "docker-compose.yml")), + "up", + "-d", + ] + + _, _, returncode = run_subprocess(command_up) + self.assertEqual(returncode, 0) diff --git a/tests/test_podman_compose_config.py b/tests/test_podman_compose_config.py index f6abf91..d04899b 100644 --- a/tests/test_podman_compose_config.py +++ b/tests/test_podman_compose_config.py @@ -6,72 +6,75 @@ # pylint: disable=redefined-outer-name import os -from test_podman_compose import capture -import pytest +from .test_podman_compose import podman_compose_path +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(test_path): +def profile_compose_file(): """ "Returns the path to the `profile` compose file used for this test module""" - return os.path.join(test_path, "profile", "docker-compose.yml") - - -def test_config_no_profiles(podman_compose_path, profile_compose_file): - """ - 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"] - - out, _, return_code = capture(config_cmd) - assert return_code == 0 - - string_output = out.decode("utf-8") - assert "default-service" in string_output - assert "service-1" not in string_output - assert "service-2" not in string_output - - -@pytest.mark.parametrize( - "profiles, expected_services", - [ - ( - ["--profile", "profile-1", "config"], - {"default-service": True, "service-1": True, "service-2": False}, - ), - ( - ["--profile", "profile-2", "config"], - {"default-service": True, "service-1": False, "service-2": True}, - ), - ( - ["--profile", "profile-1", "--profile", "profile-2", "config"], - {"default-service": True, "service-1": True, "service-2": True}, - ), - ], -) -def test_config_profiles(podman_compose_path, profile_compose_file, profiles, expected_services): - """ - 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 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. - """ - config_cmd = ["coverage", "run", podman_compose_path, "-f", profile_compose_file] - config_cmd.extend(profiles) - - out, _, return_code = capture(config_cmd) - assert return_code == 0 - - actual_output = out.decode("utf-8") - - assert len(expected_services) == 3 - - actual_services = {} - for service, _ in expected_services.items(): - actual_services[service] = service in actual_output - - assert expected_services == actual_services + return os.path.join(test_path(), "profile", "docker-compose.yml") + + +class TestComposeConfig(unittest.TestCase): + def test_config_no_profiles(self): + """ + Tests podman-compose config command without profile enablement. + """ + config_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + profile_compose_file(), + "config", + ] + + out, _, return_code = run_subprocess(config_cmd) + self.assertEqual(return_code, 0) + + string_output = out.decode("utf-8") + self.assertIn("default-service", string_output) + self.assertNotIn("service-1", string_output) + self.assertNotIn("service-2", string_output) + + @parameterized.expand( + [ + ( + ["--profile", "profile-1", "config"], + {"default-service": True, "service-1": True, "service-2": False}, + ), + ( + ["--profile", "profile-2", "config"], + {"default-service": True, "service-1": False, "service-2": True}, + ), + ( + ["--profile", "profile-1", "--profile", "profile-2", "config"], + {"default-service": True, "service-1": True, "service-2": True}, + ), + ], + ) + def test_config_profiles(self, profiles, expected_services): + """ + Tests podman-compose + :param profiles: The enabled profiles for the parameterized test. + :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. + """ + config_cmd = ["coverage", "run", podman_compose_path(), "-f", profile_compose_file()] + config_cmd.extend(profiles) + + out, _, return_code = run_subprocess(config_cmd) + self.assertEqual(return_code, 0) + + actual_output = out.decode("utf-8") + + self.assertEqual(len(expected_services), 3) + + actual_services = {} + for service, _ in expected_services.items(): + actual_services[service] = service in actual_output + + self.assertEqual(expected_services, actual_services) diff --git a/tests/test_podman_compose_include.py b/tests/test_podman_compose_include.py index 0fec773..b40e5ab 100644 --- a/tests/test_podman_compose_include.py +++ b/tests/test_podman_compose_include.py @@ -1,8 +1,9 @@ from pathlib import Path import subprocess +import unittest -def capture(command): +def run_subprocess(command): proc = subprocess.Popen( command, stdout=subprocess.PIPE, @@ -12,61 +13,62 @@ def capture(command): return out, err, proc.returncode -def test_podman_compose_include(): - """ - Test that podman-compose can execute podman-compose -f up with include - :return: - """ - main_path = Path(__file__).parent.parent +class TestPodmanComposeInclude(unittest.TestCase): + def test_podman_compose_include(self): + """ + Test that podman-compose can execute podman-compose -f up with include + :return: + """ + main_path = Path(__file__).parent.parent - command_up = [ - "coverage", - "run", - str(main_path.joinpath("podman_compose.py")), - "-f", - str(main_path.joinpath("tests", "include", "docker-compose.yaml")), - "up", - "-d", - ] + command_up = [ + "coverage", + "run", + str(main_path.joinpath("podman_compose.py")), + "-f", + str(main_path.joinpath("tests", "include", "docker-compose.yaml")), + "up", + "-d", + ] - command_check_container = [ - "podman", - "ps", - "-a", - "--filter", - "label=io.podman.compose.project=include", - "--format", - '"{{.Image}}"', - ] + command_check_container = [ + "podman", + "ps", + "-a", + "--filter", + "label=io.podman.compose.project=include", + "--format", + '"{{.Image}}"', + ] - command_container_id = [ - "podman", - "ps", - "-a", - "--filter", - "label=io.podman.compose.project=include", - "--format", - '"{{.ID}}"', - ] + command_container_id = [ + "podman", + "ps", + "-a", + "--filter", + "label=io.podman.compose.project=include", + "--format", + '"{{.ID}}"', + ] - command_down = ["podman", "rm", "--force", "CONTAINER_ID"] + command_down = ["podman", "rm", "--force", "CONTAINER_ID"] - out, _, returncode = capture(command_up) - assert 0 == returncode - out, _, returncode = capture(command_check_container) - assert 0 == returncode - assert out == b'"docker.io/library/busybox:latest"\n' - # Get container ID to remove it - out, _, returncode = capture(command_container_id) - assert 0 == returncode - assert out != b"" - container_id = out.decode().strip().replace('"', "") - command_down[3] = container_id - out, _, returncode = capture(command_down) - # cleanup test image(tags) - assert 0 == returncode - assert out != b"" - # check container did not exists anymore - out, _, returncode = capture(command_check_container) - assert 0 == returncode - assert out == b"" + out, _, returncode = run_subprocess(command_up) + self.assertEqual(returncode, 0) + out, _, returncode = run_subprocess(command_check_container) + self.assertEqual(returncode, 0) + self.assertEqual(out, b'"docker.io/library/busybox:latest"\n') + # Get container ID to remove it + out, _, returncode = run_subprocess(command_container_id) + self.assertEqual(returncode, 0) + self.assertNotEqual(out, b"") + container_id = out.decode().strip().replace('"', "") + command_down[3] = container_id + out, _, returncode = run_subprocess(command_down) + # cleanup test image(tags) + self.assertEqual(returncode, 0) + self.assertNotEqual(out, b"") + # check container did not exists anymore + out, _, returncode = run_subprocess(command_check_container) + self.assertEqual(returncode, 0) + self.assertEqual(out, b"") diff --git a/tests/test_podman_compose_tests.py b/tests/test_podman_compose_tests.py index c8db919..2a09a75 100644 --- a/tests/test_podman_compose_tests.py +++ b/tests/test_podman_compose_tests.py @@ -7,182 +7,192 @@ # pylint: disable=redefined-outer-name import os import time - -from test_podman_compose import capture - - -def test_exit_from(podman_compose_path, test_path): - up_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "exit-from", "docker-compose.yaml"), - "up", - ] - - out, _, return_code = capture(up_cmd + ["--exit-code-from", "sh1"]) - assert return_code == 1 - - out, _, return_code = capture(up_cmd + ["--exit-code-from", "sh2"]) - assert return_code == 2 - - -def test_run(podman_compose_path, test_path): - """ - This will test depends_on as well - """ - run_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "deps", "docker-compose.yaml"), - "run", - "--rm", - "sleep", - "/bin/sh", - "-c", - "wget -q -O - http://web:8000/hosts", - ] - - out, _, return_code = capture(run_cmd) - assert b'127.0.0.1\tlocalhost' in 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, - # would fail - run_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "deps", "docker-compose.yaml"), - "run", - "--rm", - "sleep", - "/bin/sh", - "-c", - "wget -q -O - http://web:8000/hosts", - ] - - out, _, return_code = capture(run_cmd) - assert b'127.0.0.1\tlocalhost' in out - assert return_code == 0 - - # This leaves a container running. Not sure it's intended, but it matches docker-compose - down_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "deps", "docker-compose.yaml"), - "down", - ] - - out, _, return_code = capture(run_cmd) - assert return_code == 0 - - -def test_up_with_ports(podman_compose_path, test_path): - up_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "ports", "docker-compose.yml"), - "up", - "-d", - "--force-recreate", - ] - - down_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "ports", "docker-compose.yml"), - "down", - "--volumes", - ] - - try: - out, _, return_code = capture(up_cmd) - assert return_code == 0 - - finally: - out, _, return_code = capture(down_cmd) - assert return_code == 0 - - -def test_down_with_vols(podman_compose_path, test_path): - up_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "vol", "docker-compose.yaml"), - "up", - "-d", - ] - - down_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "vol", "docker-compose.yaml"), - "down", - "--volumes", - ] - - try: - out, _, return_code = capture(["podman", "volume", "create", "my-app-data"]) - assert return_code == 0 - out, _, return_code = capture(["podman", "volume", "create", "actual-name-of-volume"]) - assert return_code == 0 - - out, _, return_code = capture(up_cmd) - assert return_code == 0 - - capture(["podman", "inspect", "volume", ""]) - - finally: - out, _, return_code = capture(down_cmd) - capture(["podman", "volume", "rm", "my-app-data"]) - capture(["podman", "volume", "rm", "actual-name-of-volume"]) - assert return_code == 0 - - -def test_down_with_orphans(podman_compose_path, test_path): - container_id, _, return_code = capture([ - "podman", - "run", - "--rm", - "-d", - "busybox", - "/bin/busybox", - "httpd", - "-f", - "-h", - "/etc/", - "-p", - "8000", - ]) - - down_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - os.path.join(test_path, "ports", "docker-compose.yml"), - "down", - "--volumes", - "--remove-orphans", - ] - - out, _, return_code = capture(down_cmd) - assert return_code == 0 - - _, _, exists = capture(["podman", "container", "exists", container_id.decode("utf-8")]) - - assert exists == 1 +import unittest + +from .test_podman_compose import run_subprocess +from .test_podman_compose import podman_compose_path +from .test_podman_compose import test_path + + +class TestPodmanCompose(unittest.TestCase): + def test_exit_from(self): + up_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "exit-from", "docker-compose.yaml"), + "up", + ] + + out, _, return_code = run_subprocess(up_cmd + ["--exit-code-from", "sh1"]) + self.assertEqual(return_code, 1) + + out, _, return_code = run_subprocess(up_cmd + ["--exit-code-from", "sh2"]) + self.assertEqual(return_code, 2) + + def test_run(self): + """ + This will test depends_on as well + """ + run_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "deps", "docker-compose.yaml"), + "run", + "--rm", + "sleep", + "/bin/sh", + "-c", + "wget -q -O - http://web:8000/hosts", + ] + + out, _, return_code = run_subprocess(run_cmd) + 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, + # would fail + run_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "deps", "docker-compose.yaml"), + "run", + "--rm", + "sleep", + "/bin/sh", + "-c", + "wget -q -O - http://web:8000/hosts", + ] + + out, _, return_code = run_subprocess(run_cmd) + assert b'127.0.0.1\tlocalhost' in out + self.assertEqual(return_code, 0) + + # This leaves a container running. Not sure it's intended, but it matches docker-compose + down_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "deps", "docker-compose.yaml"), + "down", + ] + + out, _, return_code = run_subprocess(run_cmd) + self.assertEqual(return_code, 0) + + def test_up_with_ports(self): + up_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "ports", "docker-compose.yml"), + "up", + "-d", + "--force-recreate", + ] + + down_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "ports", "docker-compose.yml"), + "down", + "--volumes", + ] + + try: + out, _, return_code = run_subprocess(up_cmd) + self.assertEqual(return_code, 0) + + finally: + out, _, return_code = run_subprocess(down_cmd) + self.assertEqual(return_code, 0) + + def test_down_with_vols(self): + up_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "vol", "docker-compose.yaml"), + "up", + "-d", + ] + + down_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "vol", "docker-compose.yaml"), + "down", + "--volumes", + ] + + try: + out, _, return_code = run_subprocess(["podman", "volume", "create", "my-app-data"]) + self.assertEqual(return_code, 0) + out, _, return_code = run_subprocess([ + "podman", + "volume", + "create", + "actual-name-of-volume", + ]) + self.assertEqual(return_code, 0) + + out, _, return_code = run_subprocess(up_cmd) + self.assertEqual(return_code, 0) + + run_subprocess(["podman", "inspect", "volume", ""]) + + finally: + out, _, return_code = run_subprocess(down_cmd) + run_subprocess(["podman", "volume", "rm", "my-app-data"]) + run_subprocess(["podman", "volume", "rm", "actual-name-of-volume"]) + self.assertEqual(return_code, 0) + + def test_down_with_orphans(self): + container_id, _, return_code = run_subprocess([ + "podman", + "run", + "--rm", + "-d", + "busybox", + "/bin/busybox", + "httpd", + "-f", + "-h", + "/etc/", + "-p", + "8000", + ]) + + down_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + os.path.join(test_path(), "ports", "docker-compose.yml"), + "down", + "--volumes", + "--remove-orphans", + ] + + out, _, return_code = run_subprocess(down_cmd) + self.assertEqual(return_code, 0) + + _, _, exists = run_subprocess([ + "podman", + "container", + "exists", + container_id.decode("utf-8"), + ]) + + self.assertEqual(exists, 1) diff --git a/tests/test_podman_compose_up_down.py b/tests/test_podman_compose_up_down.py index 5f0cbd3..4609196 100644 --- a/tests/test_podman_compose_up_down.py +++ b/tests/test_podman_compose_up_down.py @@ -6,87 +6,83 @@ # pylint: disable=redefined-outer-name import os -from test_podman_compose import capture -import pytest +from .test_podman_compose import run_subprocess +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(test_path): +def profile_compose_file(): """ "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) -def teardown(podman_compose_path, profile_compose_file): - """ - Ensures that the services within the "profile compose file" are removed between each test case. +class TestUpDown(unittest.TestCase): + def tearDown(self): + """ + Ensures that the services within the "profile compose file" are removed between each test case. + """ + # run the 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 - yield + down_cmd = [ + "coverage", + "run", + podman_compose_path(), + "--profile", + "profile-1", + "--profile", + "profile-2", + "-f", + profile_compose_file(), + "down", + ] + run_subprocess(down_cmd) - down_cmd = [ - "coverage", - "run", - podman_compose_path, - "--profile", - "profile-1", - "--profile", - "profile-2", - "-f", - profile_compose_file, - "down", - ] - capture(down_cmd) + @parameterized.expand( + [ + ( + ["--profile", "profile-1", "up", "-d"], + {"default-service": True, "service-1": True, "service-2": False}, + ), + ( + ["--profile", "profile-2", "up", "-d"], + {"default-service": True, "service-1": False, "service-2": True}, + ), + ( + ["--profile", "profile-1", "--profile", "profile-2", "up", "-d"], + {"default-service": True, "service-1": True, "service-2": True}, + ), + ], + ) + def test_up(self, profiles, expected_services): + up_cmd = [ + "coverage", + "run", + podman_compose_path(), + "-f", + profile_compose_file(), + ] + up_cmd.extend(profiles) + out, _, return_code = run_subprocess(up_cmd) + self.assertEqual(return_code, 0) -@pytest.mark.parametrize( - "profiles, expected_services", - [ - ( - ["--profile", "profile-1", "up", "-d"], - {"default-service": True, "service-1": True, "service-2": False}, - ), - ( - ["--profile", "profile-2", "up", "-d"], - {"default-service": True, "service-1": False, "service-2": True}, - ), - ( - ["--profile", "profile-1", "--profile", "profile-2", "up", "-d"], - {"default-service": True, "service-1": True, "service-2": True}, - ), - ], -) -def test_up(podman_compose_path, profile_compose_file, profiles, expected_services): - up_cmd = [ - "coverage", - "run", - podman_compose_path, - "-f", - profile_compose_file, - ] - up_cmd.extend(profiles) + check_cmd = [ + "podman", + "container", + "ps", + "--format", + '"{{.Names}}"', + ] + out, _, return_code = run_subprocess(check_cmd) + self.assertEqual(return_code, 0) - out, _, return_code = capture(up_cmd) - assert return_code == 0 + self.assertEqual(len(expected_services), 3) + actual_output = out.decode("utf-8") - check_cmd = [ - "podman", - "container", - "ps", - "--format", - '"{{.Names}}"', - ] - out, _, return_code = capture(check_cmd) - assert return_code == 0 + actual_services = {} + for service, _ in expected_services.items(): + actual_services[service] = service in actual_output - assert len(expected_services) == 3 - actual_output = out.decode("utf-8") - - actual_services = {} - for service, _ in expected_services.items(): - actual_services[service] = service in actual_output - - assert expected_services == actual_services + self.assertEqual(expected_services, actual_services)