From 1ffd24dcf92e4867671156a8d6bab42b0669618b Mon Sep 17 00:00:00 2001 From: Cleber Rosa Date: Wed, 1 Feb 2023 14:21:11 -0500 Subject: [PATCH 1/5] Python version support: sync verified and advertised versions There are differences with regards to the versions of Python that are verified (somehow) through Pylint, and the ones that are advertised. Given that there's no pinning of Pylint versions, it shouldn't be possible to use it on Python versions such as 3.5 and 3.6 (latest Pylint doesn't support those). With that, let's cover all the currently supported Python versions. Signed-off-by: Cleber Rosa --- .github/workflows/pylint.yml | 2 +- setup.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 85de488..957bd98 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/setup.py b/setup.py index 770649e..5222c14 100644 --- a/setup.py +++ b/setup.py @@ -16,12 +16,11 @@ setup( classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Intended Audience :: Developers", "Operating System :: OS Independent", "Development Status :: 3 - Alpha", From 517aeba330002d7816d6973026b110368b051612 Mon Sep 17 00:00:00 2001 From: Sergei Biriukov Date: Fri, 21 Apr 2023 22:23:45 +1000 Subject: [PATCH 2/5] Allow config to merge strings and lists in command and entrypoint Signed-off-by: Sergei Biriukov --- podman_compose.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/podman_compose.py b/podman_compose.py index 59ee1dc..ef21b5d 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -61,6 +61,15 @@ def is_list(list_object): ) +def is_list_of_str(list_of_str_object): + if is_list(list_of_str_object): + for element in list_of_str_object: + if not is_str(element): + return False + return True + return False + + # identity filter def filteri(a): return filter(lambda i: i, a) @@ -1307,14 +1316,25 @@ def rec_merge_one(target, source): if key not in source: continue value2 = source[key] - if key == "command": - target[key] = clone(value2) + if key in ("command", "entrypoint"): + if not is_str(value) and not is_list_of_str(value): + raise ValueError( + f"can't merge value of [{key}]: must be a string or a list of strings" + ) + if not is_str(value2) and not is_list_of_str(value2): + raise ValueError( + f"can't merge value of [{key}]: must be a string or a list of strings" + ) + if is_str(value2): + target[key] = value2.split(" ") + else: + target[key] = clone(value2) continue if not isinstance(value2, type(value)): value_type = type(value) value2_type = type(value2) raise ValueError( - f"can't merge value of {key} of type {value_type} and {value2_type}" + f"can't merge value of [{key}] of type {value_type} and {value2_type}" ) if is_list(value2): if key == "volumes": From 9011e9faa1d8718459c2c90d0f677998c7e09a3b Mon Sep 17 00:00:00 2001 From: Sergei Biriukov Date: Sun, 23 Apr 2023 20:11:26 +1000 Subject: [PATCH 3/5] add tests, dry up code, use shlex.split instead of str.split Signed-off-by: Sergei Biriukov --- podman_compose.py | 44 +++++++++----- pytests/test_is_list_of_str.py | 10 ++++ pytests/test_rec_merge_one_cmd_ent.py | 82 +++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 pytests/test_is_list_of_str.py create mode 100644 pytests/test_rec_merge_one_cmd_ent.py diff --git a/podman_compose.py b/podman_compose.py index ef21b5d..97fb60e 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -63,6 +63,8 @@ def is_list(list_object): def is_list_of_str(list_of_str_object): if is_list(list_of_str_object): + if len(list_of_str_object) == 0: + return False for element in list_of_str_object: if not is_str(element): return False @@ -1300,6 +1302,20 @@ def clone(value): return value.copy() if is_list(value) or is_dict(value) else value +def clone_shell_value(target, key, value): + if is_str(value): + target[key] = shlex.split(value) + else: + target[key] = clone(value) + + +def check_shell_value_type(key, value): + if not is_str(value) and not is_list_of_str(value): + raise ValueError( + f"can't merge value of [{key}]: must be a string or a list of strings" + ) + + def rec_merge_one(target, source): """ update target from source recursively @@ -1308,28 +1324,26 @@ def rec_merge_one(target, source): for key, value in source.items(): if key in target: continue - target[key] = clone(value) + if key in ("command", "entrypoint"): + check_shell_value_type(key, value) + clone_shell_value(target, key, value) + else: + target[key] = clone(value) done.add(key) for key, value in target.items(): if key in done: continue + if key in ("command", "entrypoint"): + if key not in source: + check_shell_value_type(key, value) + clone_shell_value(target, key, value) + else: + check_shell_value_type(key, source[key]) + clone_shell_value(target, key, source[key]) + continue if key not in source: continue value2 = source[key] - if key in ("command", "entrypoint"): - if not is_str(value) and not is_list_of_str(value): - raise ValueError( - f"can't merge value of [{key}]: must be a string or a list of strings" - ) - if not is_str(value2) and not is_list_of_str(value2): - raise ValueError( - f"can't merge value of [{key}]: must be a string or a list of strings" - ) - if is_str(value2): - target[key] = value2.split(" ") - else: - target[key] = clone(value2) - continue if not isinstance(value2, type(value)): value_type = type(value) value2_type = type(value2) diff --git a/pytests/test_is_list_of_str.py b/pytests/test_is_list_of_str.py new file mode 100644 index 0000000..e5b8d7c --- /dev/null +++ b/pytests/test_is_list_of_str.py @@ -0,0 +1,10 @@ +from podman_compose import is_list_of_str + + +def test_is_list_of_str(): + assert is_list_of_str(["foo", "bar"]) + assert not is_list_of_str(["foo", 1]) + assert not is_list_of_str("foo") + assert not is_list_of_str([]) + assert not is_list_of_str(1) + assert not is_list_of_str(None) diff --git a/pytests/test_rec_merge_one_cmd_ent.py b/pytests/test_rec_merge_one_cmd_ent.py new file mode 100644 index 0000000..0ef3717 --- /dev/null +++ b/pytests/test_rec_merge_one_cmd_ent.py @@ -0,0 +1,82 @@ +import copy + +import pytest +from podman_compose import rec_merge_one + + +test_keys = ["command", "entrypoint"] +test_cases = [ + ({}, {"$$$": "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"]}, + ), +] +test_cases_with_exceptions = [ + ({}, {"$$$": 1234}, ValueError), + ({"$$$": 1234}, {}, ValueError), + ({"$$$": 1234}, {"$$$": 1234}, ValueError), + ({"$$$": {}}, {}, ValueError), + ({}, {"$$$": {}}, ValueError), + ({"$$$": {}}, {"$$$": {}}, ValueError), + ({"$$$": []}, {}, ValueError), + ({}, {"$$$": []}, ValueError), + ({"$$$": []}, {"$$$": []}, ValueError), +] + + +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_rec_merge_one_for_command_and_entrypoint(): + for base_template, override_template, expected_template in test_cases: + for key in test_keys: + base, override, expected = template_to_expression( + base_template, override_template, expected_template, key + ) + + base = rec_merge_one(base, override) + test_result = expected == base + if not test_result: + print("base_template: ", base_template) + print("override_template: ", override_template) + print("expected: ", expected) + print("actual: ", base) + assert test_result + + for ( + base_template, + override_template, + expected_exception, + ) in test_cases_with_exceptions: + for key in test_keys: + base, override, expected = template_to_expression( + base_template, override_template, {"$$$": ""}, key + ) + + with pytest.raises(expected_exception): + base = rec_merge_one(base, override) + print("base_template: ", base_template) + print("override_template: ", override_template) + print("expected: ", expected_exception) From d1509468c3ba21f8ee4c39c6f84387e5a43dc750 Mon Sep 17 00:00:00 2001 From: Sergei Biriukov Date: Sun, 30 Apr 2023 14:56:04 +1000 Subject: [PATCH 4/5] allow empty list to be a command/entrypoint Signed-off-by: Sergei Biriukov --- podman_compose.py | 2 -- pytests/test_is_list_of_str.py | 2 +- pytests/test_rec_merge_one_cmd_ent.py | 7 ++++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/podman_compose.py b/podman_compose.py index 97fb60e..017acbd 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -63,8 +63,6 @@ def is_list(list_object): def is_list_of_str(list_of_str_object): if is_list(list_of_str_object): - if len(list_of_str_object) == 0: - return False for element in list_of_str_object: if not is_str(element): return False diff --git a/pytests/test_is_list_of_str.py b/pytests/test_is_list_of_str.py index e5b8d7c..5e5766a 100644 --- a/pytests/test_is_list_of_str.py +++ b/pytests/test_is_list_of_str.py @@ -2,9 +2,9 @@ from podman_compose import is_list_of_str def test_is_list_of_str(): + assert is_list_of_str([]) assert is_list_of_str(["foo", "bar"]) assert not is_list_of_str(["foo", 1]) assert not is_list_of_str("foo") - assert not is_list_of_str([]) assert not is_list_of_str(1) assert not is_list_of_str(None) diff --git a/pytests/test_rec_merge_one_cmd_ent.py b/pytests/test_rec_merge_one_cmd_ent.py index 0ef3717..722606d 100644 --- a/pytests/test_rec_merge_one_cmd_ent.py +++ b/pytests/test_rec_merge_one_cmd_ent.py @@ -6,6 +6,10 @@ from podman_compose import rec_merge_one test_keys = ["command", "entrypoint"] test_cases = [ + ({}, {"$$$": []}, {"$$$": []}), + ({"$$$": []}, {}, {"$$$": []}), + ({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), + ({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}), ({}, {"$$$": "sh"}, {"$$$": ["sh"]}), ({"$$$": "sh"}, {}, {"$$$": ["sh"]}), ({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}), @@ -30,9 +34,6 @@ test_cases_with_exceptions = [ ({"$$$": {}}, {}, ValueError), ({}, {"$$$": {}}, ValueError), ({"$$$": {}}, {"$$$": {}}, ValueError), - ({"$$$": []}, {}, ValueError), - ({}, {"$$$": []}, ValueError), - ({"$$$": []}, {"$$$": []}, ValueError), ] From 79bfad103c27ab2f7af26b7016ff7265806e777e Mon Sep 17 00:00:00 2001 From: Sergei Biriukov Date: Sat, 6 May 2023 15:56:15 +1000 Subject: [PATCH 5/5] move logic from rec_merge to normalize_service Signed-off-by: Sergei Biriukov --- podman_compose.py | 44 ++-------- pytests/test_can_merge_cmd_ent.py | 121 ++++++++++++++++++++++++++ pytests/test_is_list_of_str.py | 10 --- pytests/test_rec_merge_one_cmd_ent.py | 83 ------------------ 4 files changed, 129 insertions(+), 129 deletions(-) create mode 100644 pytests/test_can_merge_cmd_ent.py delete mode 100644 pytests/test_is_list_of_str.py delete mode 100644 pytests/test_rec_merge_one_cmd_ent.py diff --git a/podman_compose.py b/podman_compose.py index 017acbd..f7f24ee 100755 --- a/podman_compose.py +++ b/podman_compose.py @@ -61,15 +61,6 @@ def is_list(list_object): ) -def is_list_of_str(list_of_str_object): - if is_list(list_of_str_object): - for element in list_of_str_object: - if not is_str(element): - return False - return True - return False - - # identity filter def filteri(a): return filter(lambda i: i, a) @@ -1264,6 +1255,10 @@ def normalize_service(service, sub_dir=""): service["build"] = context else: service["build"]["context"] = context + for key in ("command", "entrypoint"): + if key in service: + if is_str(service[key]): + service[key] = shlex.split(service[key]) for key in ("env_file", "security_opt", "volumes"): if key not in service: continue @@ -1300,20 +1295,6 @@ def clone(value): return value.copy() if is_list(value) or is_dict(value) else value -def clone_shell_value(target, key, value): - if is_str(value): - target[key] = shlex.split(value) - else: - target[key] = clone(value) - - -def check_shell_value_type(key, value): - if not is_str(value) and not is_list_of_str(value): - raise ValueError( - f"can't merge value of [{key}]: must be a string or a list of strings" - ) - - def rec_merge_one(target, source): """ update target from source recursively @@ -1322,26 +1303,17 @@ def rec_merge_one(target, source): for key, value in source.items(): if key in target: continue - if key in ("command", "entrypoint"): - check_shell_value_type(key, value) - clone_shell_value(target, key, value) - else: - target[key] = clone(value) + target[key] = clone(value) done.add(key) for key, value in target.items(): if key in done: continue - if key in ("command", "entrypoint"): - if key not in source: - check_shell_value_type(key, value) - clone_shell_value(target, key, value) - else: - check_shell_value_type(key, source[key]) - clone_shell_value(target, key, source[key]) - continue if key not in source: continue value2 = source[key] + if key in ("command", "entrypoint"): + target[key] = clone(value2) + continue if not isinstance(value2, type(value)): value_type = type(value) value2_type = type(value2) diff --git a/pytests/test_can_merge_cmd_ent.py b/pytests/test_can_merge_cmd_ent.py new file mode 100644 index 0000000..5175673 --- /dev/null +++ b/pytests/test_can_merge_cmd_ent.py @@ -0,0 +1,121 @@ +import copy +import os +import argparse +import yaml +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: + 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: + base, override, expected = template_to_expression( + base_template, override_template, expected_template, key + ) + compose_test_1 = {"services": {"test-service": base}} + 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") + + podman_compose = PodmanCompose() + set_args(podman_compose, ["test-compose-1.yaml", "test-compose-2.yaml"]) + + podman_compose._parse_compose_file() # pylint: disable=protected-access + + actual = {} + 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) + + assert expected == actual + + +def set_args(podman_compose: PodmanCompose, file_names: list[str]) -> None: + podman_compose.global_args = argparse.Namespace() + podman_compose.global_args.file = file_names + podman_compose.global_args.project_name = None + podman_compose.global_args.env_file = None + podman_compose.global_args.profile = [] + podman_compose.global_args.in_pod = True + + +def dump_yaml(compose: dict, name: str) -> None: + with open(name, "w", encoding="utf-8") as outfile: + yaml.safe_dump(compose, outfile, default_flow_style=False) + + +def test_clean_test_yamls() -> None: + test_files = ["test-compose-1.yaml", "test-compose-2.yaml"] + for file in test_files: + if os.path.exists(file): + os.remove(file) diff --git a/pytests/test_is_list_of_str.py b/pytests/test_is_list_of_str.py deleted file mode 100644 index 5e5766a..0000000 --- a/pytests/test_is_list_of_str.py +++ /dev/null @@ -1,10 +0,0 @@ -from podman_compose import is_list_of_str - - -def test_is_list_of_str(): - assert is_list_of_str([]) - assert is_list_of_str(["foo", "bar"]) - assert not is_list_of_str(["foo", 1]) - assert not is_list_of_str("foo") - assert not is_list_of_str(1) - assert not is_list_of_str(None) diff --git a/pytests/test_rec_merge_one_cmd_ent.py b/pytests/test_rec_merge_one_cmd_ent.py deleted file mode 100644 index 722606d..0000000 --- a/pytests/test_rec_merge_one_cmd_ent.py +++ /dev/null @@ -1,83 +0,0 @@ -import copy - -import pytest -from podman_compose import rec_merge_one - - -test_keys = ["command", "entrypoint"] -test_cases = [ - ({}, {"$$$": []}, {"$$$": []}), - ({"$$$": []}, {}, {"$$$": []}), - ({"$$$": []}, {"$$$": "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"]}, - ), -] -test_cases_with_exceptions = [ - ({}, {"$$$": 1234}, ValueError), - ({"$$$": 1234}, {}, ValueError), - ({"$$$": 1234}, {"$$$": 1234}, ValueError), - ({"$$$": {}}, {}, ValueError), - ({}, {"$$$": {}}, ValueError), - ({"$$$": {}}, {"$$$": {}}, ValueError), -] - - -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_rec_merge_one_for_command_and_entrypoint(): - for base_template, override_template, expected_template in test_cases: - for key in test_keys: - base, override, expected = template_to_expression( - base_template, override_template, expected_template, key - ) - - base = rec_merge_one(base, override) - test_result = expected == base - if not test_result: - print("base_template: ", base_template) - print("override_template: ", override_template) - print("expected: ", expected) - print("actual: ", base) - assert test_result - - for ( - base_template, - override_template, - expected_exception, - ) in test_cases_with_exceptions: - for key in test_keys: - base, override, expected = template_to_expression( - base_template, override_template, {"$$$": ""}, key - ) - - with pytest.raises(expected_exception): - base = rec_merge_one(base, override) - print("base_template: ", base_template) - print("override_template: ", override_template) - print("expected: ", expected_exception)