diff --git a/pytest-backend/doc/changes/unreleased.md b/pytest-backend/doc/changes/unreleased.md index 79e701b..1a175a7 100644 --- a/pytest-backend/doc/changes/unreleased.md +++ b/pytest-backend/doc/changes/unreleased.md @@ -1 +1,6 @@ # Unreleased + +## Bug fixing + +* #75: Fixed the project_short_tag fixture. Now it should be able to find the error_code_config.yml + regardless of the testing rootpath. \ No newline at end of file diff --git a/pytest-backend/exasol/pytest_backend/project_short_tag.py b/pytest-backend/exasol/pytest_backend/project_short_tag.py index 31488fa..e3a9285 100644 --- a/pytest-backend/exasol/pytest_backend/project_short_tag.py +++ b/pytest-backend/exasol/pytest_backend/project_short_tag.py @@ -9,18 +9,43 @@ from pathlib import Path import yaml -FILE = "error_code_config.yml" +YML_FILE = 'error_code_config.yml' +STOP_FILE = 'pyproject.toml' -def read_from_yaml(dir: Path) -> str: +def _find_path_backwards(start_path: str | Path, + stop_file: str | Path = STOP_FILE, + target_path: str | Path = YML_FILE) -> Path | None: """ - Read project-short-tag from yaml file ``FILE`` in the specified - directory ``dir``. + An utility searching for a specified path backwards. It begins with the given start + path and checks if the target path is among its siblings. Then it moves to the parent + path and so on, until it either reaches the root of the file structure or finds the + stop file. returns None if the search is unsuccessful. """ - config_file = dir / FILE - if not config_file.exists(): + current_path = Path(start_path) + root = Path(current_path.root) + while current_path != root: + result_path = Path(current_path, target_path) + if result_path.exists(): + return result_path + stop_path = Path(current_path, stop_file) + if stop_path.exists(): + return None + current_path = current_path.parent + + +def read_from_yaml(start_dir: Path) -> str | None: + """ + Read project-short-tag from yaml file ``FILE`` looking for it from the + specified starting directory ``start_dir``. + If the yml file cannot be found returns None. + If the yml file is found, but it doesn't have the expected format raises + a RuntimeError. + """ + config_file = _find_path_backwards(start_dir) + if config_file is None: return None - with open(config_file, 'r') as file: + with config_file.open('r') as file: ecc = yaml.safe_load(file) try: return next(t for t in ecc["error-tags"]) diff --git a/pytest-backend/test/integration/pytest_backend_test.py b/pytest-backend/test/integration/pytest_backend_test.py index a6ff96b..4da128a 100644 --- a/pytest-backend/test/integration/pytest_backend_test.py +++ b/pytest-backend/test/integration/pytest_backend_test.py @@ -1,4 +1,8 @@ from textwrap import dedent +import os +import re +from inspect import cleandoc +from unittest import mock import pytest from exasol.pytest_backend import (BACKEND_OPTION, BACKEND_ALL, BACKEND_ONPREM) @@ -8,6 +12,11 @@ pytest_plugins = ["pytester"] +def _testfile(body): + test_name = re.sub(r"^.*def ([^(]+).*", "\\1", body, flags=re.S) + return { test_name: cleandoc(body) } + + def test_pytest_all_backends(pytester): test_code = dedent(""" import pyexasol @@ -76,3 +85,44 @@ def test_default_itde_options(itde_config): assert itde_config.db_disk_size == DEFAULT_ITDE_DB_DISK_SIZE assert itde_config.nameserver == [] assert itde_config.db_version == DEFAULT_ITDE_DB_VERSION + + +@pytest.mark.parametrize( + "pst_file, pst_env, pst_cli, expected", + [ + ("F", None, None, "F"), + ("F", "E", None, "E"), + ("F", "E", "C", "C"), + ]) +def test_project_short_tag( + request, + pytester, + pst_file, + pst_env, + pst_cli, + expected, +): + """ + This test sets different values for the project short tag in the file + error_code_config.yml, cli option --project-short-tag, and environment + variable PROJECT_SHORT_TAG and verifies their precedence. + """ + if pst_file: + pytester.makefile(".yml", **{ + "error_code_config": + cleandoc(f""" + error-tags: + {pst_file}: + highest-index: 0 + """) + }) + pytester.makepyfile(**_testfile( + f""" + def test_project_short_tag(project_short_tag): + assert "{expected}" == project_short_tag + """)) + env = { "PROJECT_SHORT_TAG": pst_env } if pst_env else {} + cli_args = ["--project-short-tag", pst_cli] if pst_cli else [] + with mock.patch.dict(os.environ, env): + result = pytester.runpytest(*cli_args) + assert result.ret == pytest.ExitCode.OK diff --git a/pytest-backend/test/unit/test_project_short_tag.py b/pytest-backend/test/unit/test_project_short_tag.py new file mode 100644 index 0000000..904fc91 --- /dev/null +++ b/pytest-backend/test/unit/test_project_short_tag.py @@ -0,0 +1,38 @@ +from textwrap import dedent +import pytest + +from exasol.pytest_backend.project_short_tag import read_from_yaml, YML_FILE, STOP_FILE + + +def test_read_from_yaml(tmp_path): + start_dir = tmp_path / 'start_dir' + short_tag = 'ABC' + short_tag_file = tmp_path / YML_FILE + with short_tag_file.open('w') as f: + f.write(dedent(f""" + error-tags: + {short_tag}: + highest-index: 0 + """)) + assert read_from_yaml(start_dir) == short_tag + + +def test_read_from_yaml_not_found(tmp_path): + start_dir = tmp_path / 'start_dir' + stop_file = tmp_path / STOP_FILE + with stop_file.open('w') as f: + f.write('[tool.poetry]') + assert read_from_yaml(start_dir) is None + + +def test_read_from_yaml_invalid(tmp_path): + start_dir = tmp_path / 'start_dir' + short_tag_file = tmp_path / YML_FILE + with short_tag_file.open('w') as f: + f.write(dedent(f""" + whatever: + ABC: + highest-index: 0 + """)) + with pytest.raises(RuntimeError): + read_from_yaml(start_dir) diff --git a/pytest-saas/doc/changes/unreleased.md b/pytest-saas/doc/changes/unreleased.md index e33b376..990ed3f 100644 --- a/pytest-saas/doc/changes/unreleased.md +++ b/pytest-saas/doc/changes/unreleased.md @@ -3,3 +3,8 @@ ## Internal * Relock dependencies + +## Bug fixing + +* #75: Fixed the project_short_tag fixture. Now it should be able to find the error_code_config.yml + regardless of the testing rootpath. \ No newline at end of file diff --git a/pytest-saas/exasol/pytest_saas/project_short_tag.py b/pytest-saas/exasol/pytest_saas/project_short_tag.py index 31488fa..e3a9285 100644 --- a/pytest-saas/exasol/pytest_saas/project_short_tag.py +++ b/pytest-saas/exasol/pytest_saas/project_short_tag.py @@ -9,18 +9,43 @@ from pathlib import Path import yaml -FILE = "error_code_config.yml" +YML_FILE = 'error_code_config.yml' +STOP_FILE = 'pyproject.toml' -def read_from_yaml(dir: Path) -> str: +def _find_path_backwards(start_path: str | Path, + stop_file: str | Path = STOP_FILE, + target_path: str | Path = YML_FILE) -> Path | None: """ - Read project-short-tag from yaml file ``FILE`` in the specified - directory ``dir``. + An utility searching for a specified path backwards. It begins with the given start + path and checks if the target path is among its siblings. Then it moves to the parent + path and so on, until it either reaches the root of the file structure or finds the + stop file. returns None if the search is unsuccessful. """ - config_file = dir / FILE - if not config_file.exists(): + current_path = Path(start_path) + root = Path(current_path.root) + while current_path != root: + result_path = Path(current_path, target_path) + if result_path.exists(): + return result_path + stop_path = Path(current_path, stop_file) + if stop_path.exists(): + return None + current_path = current_path.parent + + +def read_from_yaml(start_dir: Path) -> str | None: + """ + Read project-short-tag from yaml file ``FILE`` looking for it from the + specified starting directory ``start_dir``. + If the yml file cannot be found returns None. + If the yml file is found, but it doesn't have the expected format raises + a RuntimeError. + """ + config_file = _find_path_backwards(start_dir) + if config_file is None: return None - with open(config_file, 'r') as file: + with config_file.open('r') as file: ecc = yaml.safe_load(file) try: return next(t for t in ecc["error-tags"])