From 4896abcec3959ef65c0e04515047510b0aeb951e Mon Sep 17 00:00:00 2001 From: Andrey Berezin Date: Tue, 18 Jul 2023 20:38:37 +0300 Subject: [PATCH] Adding code validation targets Signed-off-by: Andrey Berezin --- .gitignore | 1 + CONTRIBUTING.md | 4 +- Makefile | 34 +++++++++++++--- pyproject.toml | 5 +++ requirements.txt | 1 + .../analytics/test_collector.py | 39 ++++++++++++------- .../analytics/test_exporter.py | 4 +- src/frostfs_testlib/hosting/docker_host.py | 18 +++++---- src/frostfs_testlib/utils/cli_utils.py | 4 +- tests/conftest.py | 5 +++ tests/helpers.py | 6 +-- 11 files changed, 80 insertions(+), 41 deletions(-) create mode 100644 tests/conftest.py diff --git a/.gitignore b/.gitignore index a7f7de02..e2967ea0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # ignore IDE files .vscode .idea +venv.* # ignore temp files under any path .DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 59968206..fdcaec70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,9 +63,9 @@ $ git checkout -b feature/123-something_awesome ``` ### Test your changes -Before submitting any changes to the library, please, make sure that all unit tests are passing. To run the tests, please, use the following command: +Before submitting any changes to the library, please, make sure that linter and all unit tests are passing. To run the tests, please, use the following command: ```shell -$ python -m unittest discover --start-directory tests +$ make validation ``` To enable tests that interact with SSH server, please, setup SSH server and set the following environment variables before running the tests: diff --git a/Makefile b/Makefile index c7466084..9dbd86ca 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ PYTHON_VERSION := 3.10 VENV_DIR := venv.frostfs-testlib current_dir := $(shell pwd) +DIRECTORIES := $(sort $(dir $(wildcard ../frostfs-testlib-plugin-*/ ../*-testcases/))) +FROM_VENV := . ${VENV_DIR}/bin/activate && venv: create requirements paths precommit @echo Ready @@ -14,14 +16,34 @@ precommit: paths: @echo Append paths for project @echo Virtual environment: ${VENV_DIR} - @sudo rm -rf ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth - @sudo touch ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth - @echo ${current_dir}/src/frostfs_testlib_frostfs_testlib | sudo tee ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth + @rm -rf ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth + @touch ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth + @echo ${current_dir}/src/frostfs_testlib_frostfs_testlib | tee ${VENV_DIR}/lib/python${PYTHON_VERSION}/site-packages/_paths.pth -create: - @echo Create virtual environment for +create: ${VENV_DIR} + +${VENV_DIR}: + @echo Create virtual environment ${VENV_DIR} virtualenv --python=python${PYTHON_VERSION} --prompt=frostfs-testlib ${VENV_DIR} requirements: @echo Isntalling pip requirements - . ${VENV_DIR}/bin/activate && pip install -Ur requirements.txt \ No newline at end of file + . ${VENV_DIR}/bin/activate && pip install -Ur requirements.txt + + +#### VALIDATION SECTION #### +lint: create requirements + ${FROM_VENV} pylint --disable R,C,W ./src + +unit_test: + @echo Starting unit tests + ${FROM_VENV} python -m pytest tests + +.PHONY: lint_dependent $(DIRECTORIES) +lint_dependent: $(DIRECTORIES) + +$(DIRECTORIES): + @echo checking dependent repo $@ + $(MAKE) validation -C $@ + +validation: lint unit_test lint_dependent \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9140ee06..8fca5332 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,3 +64,8 @@ push = false [tool.bumpver.file_patterns] "pyproject.toml" = ['current_version = "{version}"', 'version = "{version}"'] "src/frostfs_testlib/__init__.py" = ["{version}"] + +[tool.pytest.ini_options] +filterwarnings = [ + "ignore:Blowfish has been deprecated:cryptography.utils.CryptographyDeprecationWarning", +] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 5b47640c..1fdf8445 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,6 +17,7 @@ black==22.8.0 bumpver==2022.1118 isort==5.12.0 pre-commit==2.20.0 +pylint==2.17.4 # Packaging dependencies build==0.8.0 diff --git a/src/frostfs_testlib/analytics/test_collector.py b/src/frostfs_testlib/analytics/test_collector.py index 0f5398e5..56ee606b 100644 --- a/src/frostfs_testlib/analytics/test_collector.py +++ b/src/frostfs_testlib/analytics/test_collector.py @@ -6,6 +6,7 @@ from docstring_parser.google import DEFAULT_SECTIONS, Section, SectionType DEFAULT_SECTIONS.append(Section("Steps", "steps", SectionType.MULTIPLE)) + class TestCase: """ Test case object implementation for use in collector and exporters @@ -106,7 +107,9 @@ class TestCaseCollector: # Read test_case suite and section name from test class if possible and get test function from class if test.cls: suite_name = test.cls.__dict__.get("__test_case_suite_name__", suite_name) - suite_section_name = test.cls.__dict__.get("__test_case_suite_section__", suite_section_name) + suite_section_name = test.cls.__dict__.get( + "__test_case_suite_section__", suite_section_name + ) test_function = test.cls.__dict__[test.originalname] else: # If no test class, read test function from module @@ -117,7 +120,9 @@ class TestCaseCollector: test_case_title = test_function.__dict__.get("__test_case_title__", None) test_case_priority = test_function.__dict__.get("__test_case_priority__", None) suite_name = test_function.__dict__.get("__test_case_suite_name__", suite_name) - suite_section_name = test_function.__dict__.get("__test_case_suite_section__", suite_section_name) + suite_section_name = test_function.__dict__.get( + "__test_case_suite_section__", suite_section_name + ) # Parce test_steps if they define in __doc__ doc_string = parse(test_function.__doc__, style=DocstringStyle.GOOGLE) @@ -125,7 +130,9 @@ class TestCaseCollector: if doc_string.short_description: test_case_description = doc_string.short_description if doc_string.long_description: - test_case_description = f"{doc_string.short_description}\r\n{doc_string.long_description}" + test_case_description = ( + f"{doc_string.short_description}\r\n{doc_string.long_description}" + ) if doc_string.meta: for meta in doc_string.meta: @@ -140,25 +147,27 @@ class TestCaseCollector: test_case_params = test_case_call_spec.id # Format title with params if test_case_title: - test_case_title = self.__format_string_with_params__(test_case_title,test_case_call_spec.params) + test_case_title = self.__format_string_with_params__( + test_case_title, test_case_call_spec.params + ) # Format steps with params if test_case_steps: for key, value in test_case_steps.items(): - value = self.__format_string_with_params__(value,test_case_call_spec.params) + value = self.__format_string_with_params__(value, test_case_call_spec.params) test_case_steps[key] = value # If there is set basic test case attributes create TestCase and return if test_case_id and test_case_title and suite_name and suite_name: test_case = TestCase( - id=test_case_id, - title=test_case_title, - description=test_case_description, - priority=test_case_priority, - steps=test_case_steps, - params=test_case_params, - suite_name=suite_name, - suite_section_name=suite_section_name, - ) + uuid_id=test_case_id, + title=test_case_title, + description=test_case_description, + priority=test_case_priority, + steps=test_case_steps, + params=test_case_params, + suite_name=suite_name, + suite_section_name=suite_section_name, + ) return test_case # Return None if there is no enough information for return test case return None @@ -187,4 +196,4 @@ class TestCaseCollector: test_case = self.__get_test_case_from_pytest_test__(test) if test_case: test_cases.append(test_case) - return test_cases \ No newline at end of file + return test_cases diff --git a/src/frostfs_testlib/analytics/test_exporter.py b/src/frostfs_testlib/analytics/test_exporter.py index 263995c6..5a569c63 100644 --- a/src/frostfs_testlib/analytics/test_exporter.py +++ b/src/frostfs_testlib/analytics/test_exporter.py @@ -67,6 +67,6 @@ class TestExporter(ABC): steps = [{"content": value, "expected": " "} for key, value in test_case.steps.items()] if test_case_in_tms: - self.update_test_case(test_case, test_case_in_tms) + self.update_test_case(test_case, test_case_in_tms, test_suite, test_section) else: - self.create_test_case(test_case) + self.create_test_case(test_case, test_suite, test_section) diff --git a/src/frostfs_testlib/hosting/docker_host.py b/src/frostfs_testlib/hosting/docker_host.py index 94ee2ff6..3addd924 100644 --- a/src/frostfs_testlib/hosting/docker_host.py +++ b/src/frostfs_testlib/hosting/docker_host.py @@ -135,13 +135,19 @@ class DockerHost(Host): timeout=service_attributes.start_timeout, ) - def wait_for_service_to_be_in_state(self, systemd_service_name: str, expected_state: str, timeout: int) -> None: + def wait_for_service_to_be_in_state( + self, systemd_service_name: str, expected_state: str, timeout: int + ) -> None: raise NotImplementedError("Not implemented for docker") - def get_data_directory(self, service_name: str) -> str: service_attributes = self._get_service_attributes(service_name) - return service_attributes.data_directory_path + + client = self._get_docker_client() + volume_info = client.inspect_volume(service_attributes.volume_name) + volume_path = volume_info["Mountpoint"] + + return volume_path def delete_metabase(self, service_name: str) -> None: raise NotImplementedError("Not implemented for docker") @@ -159,11 +165,7 @@ class DockerHost(Host): raise NotImplementedError("Not implemented for docker") def delete_storage_node_data(self, service_name: str, cache_only: bool = False) -> None: - service_attributes = self._get_service_attributes(service_name) - - client = self._get_docker_client() - volume_info = client.inspect_volume(service_attributes.volume_name) - volume_path = volume_info["Mountpoint"] + volume_path = self.get_data_directory(service_name) shell = self.get_shell() meta_clean_cmd = f"rm -rf {volume_path}/meta*/*" diff --git a/src/frostfs_testlib/utils/cli_utils.py b/src/frostfs_testlib/utils/cli_utils.py index 7ed1a272..d869714b 100644 --- a/src/frostfs_testlib/utils/cli_utils.py +++ b/src/frostfs_testlib/utils/cli_utils.py @@ -68,9 +68,7 @@ def _cmd_run(cmd: str, timeout: int = 90) -> str: end_time = datetime.now() _attach_allure_log(cmd, cmd_output, return_code, start_time, end_time) logger.info( - f"Command: {cmd}\n" - f"Error:\nreturn code: {return_code}\n" - f"Output: {exc.output.decode('utf-8') if type(exc.output) is bytes else exc.output}" + f"Command: {cmd}\n" f"Error:\nreturn code: {return_code}\n" f"Output: {cmd_output}" ) raise diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..ea6d681e --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,5 @@ +import os +import sys + +app_dir = os.path.join(os.getcwd(), "src") +sys.path.insert(0, app_dir) diff --git a/tests/helpers.py b/tests/helpers.py index 83910026..b7776fd0 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -14,11 +14,7 @@ def format_error_details(error: Exception) -> str: Returns: String containing exception details. """ - detail_lines = traceback.format_exception( - etype=type(error), - value=error, - tb=error.__traceback__, - ) + detail_lines = traceback.format_exception(error) return "".join(detail_lines)