From acd83fcea14eca451758d8355c0abe5e5c09baa2 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Tue, 25 Aug 2026 16:41:02 +0200 Subject: [PATCH 01/13] CVS-169692_enable_on_commit_tests_part_III --- ci/build_test_OnCommit.groovy | 13 +++++++-- tests/functional/constants/ovms_messages.py | 3 ++- tests/functional/models/models.py | 3 ++- .../object_model/mediapipe_calculators.py | 27 ++----------------- tests/functional/utils/hooks.py | 24 ++++++++++++----- tests/functional/utils/test_framework.py | 24 ++++++++++++++++- tests/models/README.md | 2 +- tests/requirements.txt | 3 --- 8 files changed, 59 insertions(+), 40 deletions(-) diff --git a/ci/build_test_OnCommit.groovy b/ci/build_test_OnCommit.groovy index 5573d55ce6..2171df9d54 100644 --- a/ci/build_test_OnCommit.groovy +++ b/ci/build_test_OnCommit.groovy @@ -185,6 +185,15 @@ pipeline { sh 'make sdl-check' } } + stage('Pylint functional tests') { + agent { + label "${agent_name_linux}" + } + when { expression { functional_tests_changed == "true" } } + steps { + sh 'python3 -m venv .venv-pylint && . .venv-pylint/bin/activate && grep pylint tests/requirements.txt | pip install -r /dev/stdin && python3 -m pylint tests/functional' + } + } } } stage('Cleanup node') { @@ -344,7 +353,7 @@ pipeline { sh "make create-venv && rm -f tests/functional && ln -s ${pwd}/../tests/functional tests/functional" def cmd_venv_activate = ". .venv/bin/activate" def cmd_export = "export TT_OVMS_C_REPO_PATH=../ && export TT_RUN_REGRESSION_TESTS=True && export TT_REGRESSION_WEEKLY_TESTS=True && export TT_TARGET_DEVICE=CPU,GPU,NPU && export TT_ENABLE_UAT_TESTS=True && export TT_ENABLE_SMOKE_TESTS=False && export TT_OVMS_C_REPO_PATH=${ovms_c_repo_path} && export TT_LOGGING_LEVEL_OVMS=DEBUG && export TT_WAIT_FOR_MESSAGES_TIMEOUT=1500 && export CORE_BRANCH=${env.CHANGE_BRANCH ?: 'main'}" - def cmd_pytest = "pytest tests/non_functional/documentation -k '${test_doc_files_str}' -n 0 --dist loadgroup" + def cmd_pytest = "pytest tests/non_functional/documentation -k 'test_links_ovms or ${test_doc_files_str}' -n 0 --dist loadgroup" def cmd = "" if ( image_build_needed == "true" ) { unstash 'ovms-release-image' @@ -407,7 +416,7 @@ pipeline { def current_path = bat(returnStdout: true, script: 'cd').trim().split('\n').last().trim() def ovms_c_repo_path = bat(returnStdout: true, script: 'cd .. && cd').trim().split('\n').last().trim() def cmd_link_ovms = "(if exist ${current_path}\\tests\\functional rmdir ${current_path}\\tests\\functional) && mklink /D ${current_path}\\tests\\functional ${ovms_c_repo_path}\\tests\\functional" - def cmd_requirements = "(if not exist .venv virtualenv .venv --python=python3.12) && call .venv\\Scripts\\activate.bat && pip install -r requirements.txt" + def cmd_requirements = "(if not exist .venv virtualenv .venv --python=python3.12) && call .venv\\Scripts\\activate.bat && pip install -r ${ovms_c_repo_path}\\tests\\requirements.txt -r requirements.txt" def cmd_export = "set \"TT_OVMS_C_REPO_PATH=../\" && set \"TT_LOGGING_LEVEL_OVMS=DEBUG\" && set \"TT_RUN_REGRESSION_TESTS=True\" && set \"TT_REGRESSION_WEEKLY_TESTS=True\" && set \"TT_TARGET_DEVICE=CPU,GPU,NPU\" && set \"TT_BASE_OS=windows\" && set \"TT_OVMS_TYPE=BINARY\" && set \"TT_ENABLE_UAT_TESTS=True\" && set \"TT_ENABLE_SMOKE_TESTS=False\" && set \"TT_DISABLE_DMESG_LOG_MONITOR=True\" && set \"TT_OVMS_C_REPO_PATH=${ovms_c_repo_path}\" && set \"TT_WAIT_FOR_MESSAGES_TIMEOUT=1500\" && set \"PYTHONUTF8=1\" && set \"PYTHONIOENCODING=utf-8\" && set \"CORE_BRANCH=${env.CHANGE_BRANCH ?: 'main'}\"" def cmd_pytest = "pytest tests/non_functional/documentation -k \"${test_doc_files_str}\" -n 0 --dist loadgroup --basetemp=\"C:\\tmp\\pytest-${BRANCH_NAME}-${BUILD_NUMBER}\"" def cmd = "" diff --git a/tests/functional/constants/ovms_messages.py b/tests/functional/constants/ovms_messages.py index 7205992b82..3b2265e6dd 100644 --- a/tests/functional/constants/ovms_messages.py +++ b/tests/functional/constants/ovms_messages.py @@ -222,7 +222,8 @@ class OvmsMessages: ERROR_CFG_KEY_ERROR = "Keyword:{} Key: #{}" USE_CONFIG_PATH_OR_MODEL_PATH_WITH_SPARE_MODEL = "Use either config_path or model_path with model_name" USE_CONFIG_PATH_WITHOUT_MODEL = "Use config_path or model_path with model_name" - ERROR_LOADING_MODEL = "Error occurred while loading model: {}" + ERROR_LOADING_MODEL_NO_VALID_MODEL = "Error loading model: no valid model file found for model" + RROR_LOADING_MODEL = "Error occurred while loading model: {}" ERROR_LOADING_MODEL_INTERNAL_SERVER_ERROR = ( "Error occurred while loading model: {}; version: {}; error: Internal server error" ) diff --git a/tests/functional/models/models.py b/tests/functional/models/models.py index 8f94e1f006..cd893b0e31 100644 --- a/tests/functional/models/models.py +++ b/tests/functional/models/models.py @@ -42,6 +42,7 @@ from tests.functional.object_model.test_environment import TestEnvironment from tests.functional.utils.helpers import get_base_device from tests.functional.utils.logger import get_logger +from tests.functional.utils.test_framework import copy_dir_tree logger = get_logger(__name__) @@ -478,7 +479,7 @@ def prepare_resources(self, base_location): target_model_dir = Path(resource_destination, *model_subpath) if not os.path.exists(target_model_dir): logger.debug(f"Copying {self.name} to container: {target_model_dir}") - shutil.copytree(src_model_path, target_model_dir, dirs_exist_ok=True) + copy_dir_tree(src_model_path, target_model_dir) for file in target_model_dir.glob("*"): # resource files from shared folder should be read only. # Add proper access for test container folder manipulations. diff --git a/tests/functional/object_model/mediapipe_calculators.py b/tests/functional/object_model/mediapipe_calculators.py index 6812dd6438..f8696e6fce 100644 --- a/tests/functional/object_model/mediapipe_calculators.py +++ b/tests/functional/object_model/mediapipe_calculators.py @@ -21,9 +21,8 @@ from pathlib import Path from typing import List -from tests.functional.utils.assertions import InvalidReturnCodeException from tests.functional.utils.logger import get_logger -from tests.functional.utils.process import Process +from tests.functional.utils.test_framework import copy_dir_tree from tests.functional.constants.generative_ai import GenerativeAIPluginConfig from tests.functional.config import ( @@ -585,34 +584,12 @@ def create_proto_content(self, model, input_stream=None, output_stream=None, cre content = self.create_node_content(header, input_streams, output_streams) return content - @staticmethod - def _copy_model_tree(proc, src, dst): - if "C:\\" in src: - proc.run_and_check( - # /R:2 - retry 2 times - # /W:3 - wait 3 seconds between retries - f"robocopy /J /E /NP /NFL /NJH /R:2 /W:3 \"{src}\" \"{dst}\"", - env=os.environ.copy(), - exit_code_check=1, - exception_type=InvalidReturnCodeException, - timeout=1800, - ) - else: - shutil.copytree(src, dst) - def prepare_resources(self, base_location): dst_base = Path(base_location, "models") dst = Path(dst_base, f"./{self.model.name}") dst.parent.mkdir(exist_ok=True, parents=True) if not Path.exists(dst): - proc = Process() - proc.disable_check_stderr() - try: - self._copy_model_tree(proc, self.models_path, dst) - except Exception as e: # pylint: disable=broad-exception-caught - if dst.exists(): - shutil.rmtree(dst, ignore_errors=True) - raise e + copy_dir_tree(Path(self.models_path), dst) return str(dst_base) @classmethod diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index 3eb636e148..d64f089297 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -31,10 +31,6 @@ from _pytest.python import Function from tests.functional import config -from tests.functional.models.models_library import ModelsLib, ModelsLibrary -from tests.functional.utils.download import wget_file -from tests.functional.utils.reservation_manager.args import parse_args -from tests.functional.utils.reservation_manager.manager import Manager as ReservationManager from tests.functional.config import ( build_test_image, c_api_wrapper_dir, @@ -89,10 +85,15 @@ from tests.functional.constants.paths import Paths from tests.functional.constants.target_device import MAX_WORKERS_PER_TARGET_DEVICE, TargetDevice from tests.functional.constants.ovms_binaries import calculate_ovms_binary_name +from tests.functional.models.models_library import ModelsLib, ModelsLibrary +from tests.functional.object_model.dmesg_log_monitor import DmesgLogMonitor from tests.functional.object_model.ovms_info import OvmsInfo +from tests.functional.object_model.ovsa import OvsaCerts from tests.functional.utils.core import TmpDir from tests.functional.utils.docker import DockerClient, DockerContainer, DOCKER_CONTAINER_TMP_PATH +from tests.functional.utils.download import wget_file from tests.functional.utils.environment_info import EnvironmentInfo +from tests.functional.utils.helpers import get_base_device from tests.functional.utils.logger import get_logger from tests.functional.utils.marks import ( MarkConditionalRunType, @@ -103,9 +104,9 @@ ) from tests.functional.utils.ov_hf_downloader import OVHfDownloader from tests.functional.utils.process import PID_STATE_ZOMBIE, Process, get_pid_name, get_pid_status +from tests.functional.utils.reservation_manager.args import parse_args +from tests.functional.utils.reservation_manager.manager import Manager as ReservationManager from tests.functional.utils.test_framework import change_dir_permissions, get_test_object_prefix, is_xdist_master -from tests.functional.utils.helpers import get_base_device -from tests.functional.object_model.ovsa import OvsaCerts logger = get_logger(__name__) @@ -151,6 +152,7 @@ def init_environment(_config): if not machine_is_reserved_for_test_session: return init_cleanup() + dmesg_cleanup() def init_cleanup(): @@ -161,6 +163,16 @@ def init_cleanup(): cleanup_docker(cleanup_docker_containers) +def dmesg_cleanup(): + if all([ + config.cleanup_env_on_startup, + not config.disable_dmesg_log_monitor, + get_host_os() != OsType.Windows, + ]): + dmesg_log_monitor = DmesgLogMonitor() + dmesg_log_monitor.clear_dmesg_buffer() + + def clean_container(container): try: container.stop(timeout=1) diff --git a/tests/functional/utils/test_framework.py b/tests/functional/utils/test_framework.py index 66622bc1cb..8ca6a15823 100644 --- a/tests/functional/utils/test_framework.py +++ b/tests/functional/utils/test_framework.py @@ -26,7 +26,7 @@ import pytest -from tests.functional.utils.assertions import CreateVenvError, PipInstallError +from tests.functional.utils.assertions import CreateVenvError, InvalidReturnCodeException, PipInstallError from tests.functional.utils.git_operations import clone_git_repository from tests.functional.utils.logger import get_logger from tests.functional.utils.process import Process, WindowsProcess @@ -277,6 +277,28 @@ def _make_path_writable_and_retry(func, path, _exc_info): func(path) +def copy_dir_tree(src, dst, timeout=1800): + proc = Process() + proc.disable_check_stderr() + try: + if "c:\\" in src.anchor.lower(): + proc.run_and_check( + # /R:2 - retry 2 times + # /W:3 - wait 3 seconds between retries + f"robocopy /J /E /NP /NFL /NJH /R:2 /W:3 \"{src}\" \"{dst}\"", + env=os.environ.copy(), + exit_code_check=1, + exception_type=InvalidReturnCodeException, + timeout=timeout, + ) + else: + shutil.copytree(src, dst) + except Exception as e: # pylint: disable=broad-exception-caught + if dst.exists(): + shutil.rmtree(dst, ignore_errors=True) + raise e + + def remove_dir_tree(dir_path, ignore_errors=False): """Remove a directory tree, retrying failed paths after making them writable.""" # shutil.rmtree accepts the `onexc` callback only on Python 3.12+; diff --git a/tests/models/README.md b/tests/models/README.md index 10e0372689..bcf9113eeb 100644 --- a/tests/models/README.md +++ b/tests/models/README.md @@ -5,7 +5,7 @@ ```bash git clone https://github.com/openvinotoolkit/model_server.git cd model_server/tests/models -pip3 install -r ../requirements.txt +pip3 install -r requirements.txt ``` ## Model incrementing an input tensor diff --git a/tests/requirements.txt b/tests/requirements.txt index b723d7c87e..d8e312c724 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -25,7 +25,4 @@ requests-toolbelt==1.0.0 retry==0.9.2 setuptools==83.0.0 soundfile==0.14.0 -tensorboard==2.20.0 -tensorflow==2.21.0 -tensorflow-serving-api==2.20.0 tritonclient[all]==2.69.0 From 57a158e7bd03fe7018fba4127357189c75475d05 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 26 Aug 2026 10:21:50 +0200 Subject: [PATCH 02/13] pytlint fixes part 1 --- ci/build_test_OnCommit.groovy | 2 +- tests/functional/config.py | 9 +++++---- tests/functional/conftest.py | 2 ++ tests/functional/object_model/custom_loader.py | 14 +++++++------- .../functional/object_model/ovms_mapping_config.py | 10 +++++----- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/ci/build_test_OnCommit.groovy b/ci/build_test_OnCommit.groovy index 2171df9d54..48d2b943c2 100644 --- a/ci/build_test_OnCommit.groovy +++ b/ci/build_test_OnCommit.groovy @@ -191,7 +191,7 @@ pipeline { } when { expression { functional_tests_changed == "true" } } steps { - sh 'python3 -m venv .venv-pylint && . .venv-pylint/bin/activate && grep pylint tests/requirements.txt | pip install -r /dev/stdin && python3 -m pylint tests/functional' + sh 'python3 -m venv .venv-pylint && . .venv-pylint/bin/activate && grep pylint tests/requirements.txt | pip install -r /dev/stdin && python3 -m pylint --rcfile=tests/functional/pylintrc tests/functional' } } } diff --git a/tests/functional/config.py b/tests/functional/config.py index e37c02cf29..94cc755d56 100644 --- a/tests/functional/config.py +++ b/tests/functional/config.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=pointless-string-statement import os import re @@ -33,14 +34,14 @@ try: # In user_config.py, user might export custom environment variables - import user_config + import user_config # pylint: disable=unused-import except ImportError: pass def get_uses_mapping(): _uses_mapping = get_list("TT_USES_MAPPING", fallback=[None]) - _uses_mapping = list(set([str(x).upper() for x in _uses_mapping])) # make upper & remove duplicates + _uses_mapping = list({str(x).upper() for x in _uses_mapping}) # make upper & remove duplicates # Reduce to True/False/None _uses_mapping = [x == "TRUE" if x in ["TRUE", "FALSE"] else None for x in _uses_mapping] validate_supported_values(_uses_mapping, [True, False, None]) @@ -52,14 +53,14 @@ def get_uses_mapping(): Possible TT_USES_MAPPING values (case insensitive): - (empty)/""/NONE - Default leave mapping.json provided alongside model untouched (if exists). - FALSE - forcibly remove mapping.json if provided with model. - - TRUE - remove any previous mapping and add generic mapping.json + - TRUE - remove any previous mapping and add generic mapping.json (see: ovms/object_model/ovms_mapping_config.py for details) - TRUE,FALSE,NONE - Iterate each test case from listed values in single test session. """ uses_mapping = get_uses_mapping() """TEST_DIR - location where models and test data should be copied from TEST_DIR_CACHE and deleted after tests""" -test_dir = os.environ.get("TEST_DIR", "/tmp/{}".format(generate_test_object_name(prefix='ovms_models'))) +test_dir = os.environ.get("TEST_DIR", f"/tmp/{generate_test_object_name(prefix='ovms_models')}") """TEST_DIR_CACHE - location where models and test data should be downloaded to and serve as cache for TEST_DIR""" test_dir_cache = os.environ.get("TEST_DIR_CACHE", "/tmp/ovms_models_cache") diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 1bb8d68b10..8fc805e772 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -14,10 +14,12 @@ # limitations under the License. # +import pytest import random import sys from tests.functional.config import enable_pytest_plugins, pytest_keyword_filter, machine_is_reserved_for_test_session +from tests.functional.constants.components import OvmsComponents from tests.functional.constants.ovms import ( CURRENT_TARGET_DEVICE_DICT_ARGUMENT, TMP_REPOS_DIR_ARGUMENT, diff --git a/tests/functional/object_model/custom_loader.py b/tests/functional/object_model/custom_loader.py index 8ca05ceb99..374afa0f68 100644 --- a/tests/functional/object_model/custom_loader.py +++ b/tests/functional/object_model/custom_loader.py @@ -87,7 +87,7 @@ def model_options(self): def get_volume_mount(self): loader_container_path = self.loader_config.get_loader_container_path() - return {self.loader_host_path: {"bind": loader_container_path, "mode": "ro"}} + return {self.loader_host_path: {"bind": loader_container_path, "mode": "ro"}} # pylint: disable=no-member def get_enable_file_name(self): return self._model_options["custom_loader_options"].get(CustomLoader.ModelOptions.ENABLE_FILE_KEY, "") @@ -121,18 +121,18 @@ def get_model_enable_file_path(self, container_name): model_enable_file_path = os.path.join(model_path_on_host, self.get_enable_file_name()) return model_enable_file_path - def enable_model(self, container_name, delete_enable_file=False, ovms_run=None): + def enable_model(self, container_name, delete_enable_file=False, ovms_run=None): # pylint: disable=unused-argument logger.debug(f"Enable model {self.name} in container {container_name}") model_enable_file_path = self.get_model_enable_file_path(container_name) if delete_enable_file: Path(model_enable_file_path).unlink("") else: - Path(model_enable_file_path).write_text("") + Path(model_enable_file_path).write_text("", encoding="utf-8") - def disable_model(self, container_name, ovms_run=None): + def disable_model(self, container_name, ovms_run=None): # pylint: disable=unused-argument logger.debug(f"Disable model {self.name} in container {container_name}") model_enable_file_path = self.get_model_enable_file_path(container_name) - Path(model_enable_file_path).write_text(CustomLoader.ENABLE_FILE_DISABLE_PHRASE) + Path(model_enable_file_path).write_text(CustomLoader.ENABLE_FILE_DISABLE_PHRASE, encoding="utf-8") def add_enable_file_entry(self, enable_file_value): self.model_options["custom_loader_options"]["enable_file"] = enable_file_value @@ -148,7 +148,7 @@ class LoaderConfig(dict): def __init__(self, name: str, loader_container_path: str, loader_config_file: str = None): super().__init__() - config = dict() + config = {} config.update({self.LOADER_NAME_KEY: name, self.LIBRARY_PATH_KEY: loader_container_path}) if loader_config_file: config.update({self.LOADER_CONFIG_FILE_KEY: loader_config_file}) @@ -173,7 +173,7 @@ class ModelOptions(dict): def __init__(self, loader_name: str = None, enable_file: str = None): super().__init__() - config = dict() + config = {} config.update({self.LOADER_NAME_KEY: loader_name}) if enable_file: config.update({self.ENABLE_FILE_KEY: enable_file}) diff --git a/tests/functional/object_model/ovms_mapping_config.py b/tests/functional/object_model/ovms_mapping_config.py index 9e7e26cdc0..ee58446e67 100644 --- a/tests/functional/object_model/ovms_mapping_config.py +++ b/tests/functional/object_model/ovms_mapping_config.py @@ -23,7 +23,7 @@ logger = get_logger(__name__) -class OvmsMappingConfig(object): +class OvmsMappingConfig: FILE_NAME = "mapping_config.json" @staticmethod @@ -97,21 +97,21 @@ def delete_mapping(ovms_container, model): def save(config_dict: dict, ovms_container, model): file_dst_path = OvmsMappingConfig.mapping_config_path(ovms_container, model) - logger.info("Saving config file to {}, content:\n{}".format(file_dst_path, config_dict)) + logger.info(f"Saving config file to {file_dst_path}, content:\n{config_dict}") - with open(file_dst_path, "w") as fp: + with open(file_dst_path, "w", encoding="utf-8") as fp: json.dump(config_dict, fp, indent=2) return file_dst_path @staticmethod def load_config(config_path): - with open(config_path, "r") as f: + with open(config_path, "r", encoding="utf-8") as f: config_json = f.read() try: config_dict = json.loads(config_json) except ValueError as e: - logger.error("Error while loading json: {}".format(config_json)) + logger.error(f"Error while loading json: {config_json}") raise e return config_dict From f9dcd6beeef8998dc8918d6ece259532180f12dd Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 26 Aug 2026 11:02:49 +0200 Subject: [PATCH 03/13] pytlint fixes part 2 --- ci/build_test_OnCommit.groovy | 2 +- tests/functional/constants/metrics.py | 8 +-- tests/functional/constants/ovms.py | 3 +- tests/functional/constants/ovms_images.py | 4 +- tests/functional/constants/ovms_openai.py | 15 ++---- tests/functional/constants/pipelines.py | 18 +++---- .../data/ovms_capi_wrapper/ovms_autopxd.py | 4 +- tests/functional/fixtures/api_type.py | 25 +++++----- tests/functional/fixtures/server.py | 2 +- .../object_model/inference_helpers.py | 37 ++++++-------- .../object_model/mediapipe_calculators.py | 22 +++++---- tests/functional/object_model/ovms_binary.py | 2 +- tests/functional/object_model/ovms_capi.py | 19 ++++--- tests/functional/object_model/ovms_command.py | 43 ++++++++-------- tests/functional/object_model/ovms_config.py | 18 +++---- tests/functional/object_model/ovms_docker.py | 28 +++++------ tests/functional/object_model/ovms_info.py | 2 +- .../functional/object_model/ovms_instance.py | 13 ++--- tests/functional/object_model/ovms_params.py | 4 +- .../object_model/package_manager.py | 16 +++--- .../object_model/resource_monitor.py | 18 +++---- .../object_model/test_environment.py | 6 +-- tests/functional/object_model/test_helpers.py | 2 +- tests/functional/pylintrc | 1 + tests/functional/utils/assertions.py | 2 +- tests/functional/utils/context.py | 2 +- tests/functional/utils/core.py | 7 ++- tests/functional/utils/docker.py | 49 ++++++++----------- tests/functional/utils/hooks.py | 14 +++--- .../functional/utils/http/client_auth/auth.py | 6 +-- .../functional/utils/http/client_auth/base.py | 2 +- tests/functional/utils/http/http_client.py | 2 +- .../utils/http/http_client_configuration.py | 2 +- .../utils/http/http_client_factory.py | 2 +- tests/functional/utils/http/http_session.py | 2 +- tests/functional/utils/log_monitor.py | 5 +- tests/functional/utils/logger.py | 2 +- tests/functional/utils/marks.py | 5 +- tests/functional/utils/numpy_loader.py | 7 ++- tests/functional/utils/process.py | 2 +- .../utils/reservation_manager/locker.py | 2 +- .../utils/reservation_manager/manager.py | 12 ++--- 42 files changed, 193 insertions(+), 244 deletions(-) diff --git a/ci/build_test_OnCommit.groovy b/ci/build_test_OnCommit.groovy index 48d2b943c2..7737f36203 100644 --- a/ci/build_test_OnCommit.groovy +++ b/ci/build_test_OnCommit.groovy @@ -191,7 +191,7 @@ pipeline { } when { expression { functional_tests_changed == "true" } } steps { - sh 'python3 -m venv .venv-pylint && . .venv-pylint/bin/activate && grep pylint tests/requirements.txt | pip install -r /dev/stdin && python3 -m pylint --rcfile=tests/functional/pylintrc tests/functional' + sh 'python3 -m venv .venv-pylint && . .venv-pylint/bin/activate && pip install -r tests/requirements.txt && python3 -m pylint --rcfile=tests/functional/pylintrc tests/functional' } } } diff --git a/tests/functional/constants/metrics.py b/tests/functional/constants/metrics.py index 82a51b83b9..96a22f3301 100644 --- a/tests/functional/constants/metrics.py +++ b/tests/functional/constants/metrics.py @@ -480,7 +480,7 @@ def find_metric_specific_value_content(self, metric_name, api, interface, method logger.debug(f"Found expected value={value} in metric {metric_name} for method {method}") metric_found = True break - assert metric_found, f"No metric found" + assert metric_found, "No metric found" def verify_metric_values(self, value): for metric in self.list: @@ -489,9 +489,6 @@ def verify_metric_values(self, value): class DefaultMetrics(Metrics): - def __init__(self): - super().__init__() - @staticmethod def create_from_model_list(model_list): return Metrics.create_from_model_list(model_list, metrics=Metric.Default_names) @@ -500,9 +497,6 @@ def create_from_model_list(model_list): class AdditionalMetrics(Metrics): Names = ["ovms_infer_req_queue_size", "ovms_infer_req_active"] - def __init__(self): - super().__init__() - # Output example # diff --git a/tests/functional/constants/ovms.py b/tests/functional/constants/ovms.py index e19e6131d8..5e417b18e5 100644 --- a/tests/functional/constants/ovms.py +++ b/tests/functional/constants/ovms.py @@ -283,8 +283,7 @@ def set_plugin_config_boolean_value(plugin_config_str, config_file=False): plugin_config_str, ) return plugin_config_str - else: - return plugin_config_str.replace('\\"false\\"', "false").replace('\\"true\\"', "true") + return plugin_config_str.replace('\\"false\\"', "false").replace('\\"true\\"', "true") def get_model_base_path(model_base_path, context, ovms_run): diff --git a/tests/functional/constants/ovms_images.py b/tests/functional/constants/ovms_images.py index dcfc06367e..ca91441b1d 100644 --- a/tests/functional/constants/ovms_images.py +++ b/tests/functional/constants/ovms_images.py @@ -110,7 +110,7 @@ def _get_os_type_and_version(cls): def calculate_ovms_image_suffix(target_device): if is_nginx_mtls: return DEFAULT_OVMS_IMAGE_SUFFIXES[NGINX] - elif ct.is_gpu_based_target(target_device) or ct.is_npu_target(): + if ct.is_gpu_based_target(target_device) or ct.is_npu_target(): return DEFAULT_OVMS_IMAGE_SUFFIXES[TargetDevice.GPU] return "" @@ -142,7 +142,7 @@ def calculate_ovms_image_name(target_device=None, base_os=OsType.Ubuntu22): if force_use_ovms_image and ovms_image: return ovms_image - elif ovms_image: + if ovms_image: image_name = re.sub("|".join(DEFAULT_OVMS_IMAGE_SUFFIXES.values()), "", ovms_image.split(":")[0]) image_tag = ovms_image.split(":")[1] image_name = f"{image_name}{calculate_ovms_image_suffix(target_device)}" diff --git a/tests/functional/constants/ovms_openai.py b/tests/functional/constants/ovms_openai.py index 144dcd2d83..13b595e33a 100644 --- a/tests/functional/constants/ovms_openai.py +++ b/tests/functional/constants/ovms_openai.py @@ -154,8 +154,7 @@ def prepare_dict(self, set_null_values=False, use_extra_body=True): return self.prepare_dict_with_extra_body( [OpenAICommonCompletionsRequestParams, OpenAIChatCompletionsRequestParams], ) - else: - return super().prepare_dict(set_null_values=set_null_values) + return super().prepare_dict(set_null_values=set_null_values) def set_default_values(self, **kwargs): super().set_default_values(**kwargs) @@ -173,8 +172,7 @@ def prepare_dict(self, set_null_values=False, use_extra_body=True): return self.prepare_dict_with_extra_body( [OpenAICommonCompletionsRequestParams, OpenAICompletionsRequestParams], ) - else: - return super().prepare_dict(set_null_values=set_null_values) + return super().prepare_dict(set_null_values=set_null_values) @dataclass @@ -205,8 +203,7 @@ def set_default_values(self, **kwargs): def prepare_dict(self, set_null_values=False, use_extra_body=True): if use_extra_body: return self.prepare_dict_with_extra_body([OpenAIResponsesRequestParams]) - else: - return super().prepare_dict(set_null_values=set_null_values) + return super().prepare_dict(set_null_values=set_null_values) @dataclass @@ -238,8 +235,7 @@ def prepare_dict(self, set_null_values=False, use_extra_body=True): return self.prepare_dict_with_extra_body( [OpenAICommonImagesRequestParams, OpenAIImagesGenerationsRequestParams], ) - else: - return super().prepare_dict(set_null_values=set_null_values) + return super().prepare_dict(set_null_values=set_null_values) @dataclass @@ -251,8 +247,7 @@ def prepare_dict(self, set_null_values=False, use_extra_body=True): return self.prepare_dict_with_extra_body( [OpenAICommonImagesRequestParams, OpenAIImagesEditsRequestParams], ) - else: - return super().prepare_dict(set_null_values=set_null_values) + return super().prepare_dict(set_null_values=set_null_values) def set_default_values(self, **kwargs): super().set_default_values(**kwargs) diff --git a/tests/functional/constants/pipelines.py b/tests/functional/constants/pipelines.py index 49d25b3528..651839282b 100644 --- a/tests/functional/constants/pipelines.py +++ b/tests/functional/constants/pipelines.py @@ -134,22 +134,20 @@ def __str__(self): def get_input_name(self, id): if self.input_names: return self.input_names[id] + if self.node_type == NodeType.Output: + prefix = "output" else: - if self.node_type == NodeType.Output: - prefix = "output" - else: - prefix = "input" - return f"{prefix}_{id}" + prefix = "input" + return f"{prefix}_{id}" def get_output_name(self, id): if self.output_names: return self.output_names[id] + if self.node_type == NodeType.Input: + prefix = "input" else: - if self.node_type == NodeType.Input: - prefix = "input" - else: - prefix = self.model.name - return f"{prefix}_{id}" + prefix = self.model.name + return f"{prefix}_{id}" def _change_name(self, names, old_name, new_name): for index, name in enumerate(names): diff --git a/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py b/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py index 081d9c40bf..5946098ca1 100644 --- a/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py +++ b/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py @@ -73,5 +73,5 @@ def translate(self, code): input_file_path = Path(args.input_file) output_file_path = Path(args.output_file) - with open(output_file_path, "w") as file_object: - file_object.write(OvmsAutoPxd(input_file_path.name).translate(input_file_path.read_text())) + with open(output_file_path, "w", encoding="utf-8") as file_object: + file_object.write(OvmsAutoPxd(input_file_path.name).translate(input_file_path.read_text(encoding="utf-8"))) diff --git a/tests/functional/fixtures/api_type.py b/tests/functional/fixtures/api_type.py index 8eac952f9e..4798936531 100644 --- a/tests/functional/fixtures/api_type.py +++ b/tests/functional/fixtures/api_type.py @@ -33,59 +33,58 @@ def api_type_non_fixture(serving, communication, ovms_type=None): _possible_api_types += [OvmsType.CAPI] -@pytest.fixture(scope="session", params=_possible_api_types, ids=lambda x: f":".join(x).upper() if len(x) == 2 else x) +@pytest.fixture(scope="session", params=_possible_api_types, ids=lambda x: ":".join(x).upper() if len(x) == 2 else x) def api_type(request): if request.param == OvmsType.CAPI: return api_type_non_fixture(serving=None, communication=None, ovms_type=request.param) - else: - return api_type_non_fixture(*request.param, ovms_type=None) + return api_type_non_fixture(*request.param, ovms_type=None) -@pytest.fixture(scope="session", params=itertools.product([KFS], [REST]), ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=itertools.product([KFS], [REST]), ids=lambda x: ":".join(x).upper()) def rest_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=itertools.product([KFS], [GRPC]), ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=itertools.product([KFS], [GRPC]), ids=lambda x: ":".join(x).upper()) def grpc_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=[(KFS, GRPC)], ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=[(KFS, GRPC)], ids=lambda x: ":".join(x).upper()) def kfs_grpc_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=[(KFS, REST)], ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=[(KFS, REST)], ids=lambda x: ":".join(x).upper()) def kfs_rest_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=itertools.product([KFS], [GRPC, REST]), ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=itertools.product([KFS], [GRPC, REST]), ids=lambda x: ":".join(x).upper()) def kfs_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=[(OPENAI, REST)], ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=[(OPENAI, REST)], ids=lambda x: ":".join(x).upper()) def openai_rest_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=[(COHERE, REST)], ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=[(COHERE, REST)], ids=lambda x: ":".join(x).upper()) def cohere_rest_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=itertools.product([TRITON], [GRPC, REST]), ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=itertools.product([TRITON], [GRPC, REST]), ids=lambda x: ":".join(x).upper()) def triton_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=[(TRITON, GRPC)], ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=[(TRITON, GRPC)], ids=lambda x: ":".join(x).upper()) def triton_grpc_api_type(request): return api_type_non_fixture(*request.param) -@pytest.fixture(scope="session", params=[(TRITON, REST)], ids=lambda x: f":".join(x).upper()) +@pytest.fixture(scope="session", params=[(TRITON, REST)], ids=lambda x: ":".join(x).upper()) def triton_rest_api_type(request): return api_type_non_fixture(*request.param) diff --git a/tests/functional/fixtures/server.py b/tests/functional/fixtures/server.py index e4e4bea13a..d968c609bf 100644 --- a/tests/functional/fixtures/server.py +++ b/tests/functional/fixtures/server.py @@ -139,7 +139,7 @@ def start_ovms( if ensure_started: assert not parameters.check_version, "OVMS container will not start if --version argument was given." log_fixture( - "Ensure ovms is running with model(s): {}".format(", ".join([model.name for model in result.models])) + f"Ensure ovms is running with model(s): {', '.join([model.name for model in result.models])}" ) result.ovms.ensure_started(result.models, timeout=timeout, os_type=context.base_os) diff --git a/tests/functional/object_model/inference_helpers.py b/tests/functional/object_model/inference_helpers.py index b368a24ecf..ab1f5ff5ee 100644 --- a/tests/functional/object_model/inference_helpers.py +++ b/tests/functional/object_model/inference_helpers.py @@ -84,7 +84,7 @@ logger = get_logger(__name__) -class InferenceBuilder(object): +class InferenceBuilder: def __init__(self, model): self.model = model @@ -143,7 +143,7 @@ def create_kfs_client(self, api_type, port): @dataclass(frozen=False) -class InferenceRequest(object): +class InferenceRequest: ovms: OvmsInstance = None model: ModelInfo = None api_type: object = None @@ -258,7 +258,7 @@ def _create_kfs_post_request(self, input_data): binary_data = b"" request_body = struct.pack( - "{}s{}s".format(len(request_header), len(binary_data)), request_header.encode(), binary_data + f"{len(request_header)}s{len(binary_data)}s", request_header.encode(), binary_data ) return { "request": request_body, @@ -266,7 +266,7 @@ def _create_kfs_post_request(self, input_data): } def load_data(self): - result = dict() + result = {} for param_name, param_data in self.model.inputs.items(): result[param_name] = self.dataset.get_data( param_data["shape"], self.batch_size, self.model.transpose_axes, None @@ -365,8 +365,7 @@ def prepare_request_parameters_dict(self, set_null_values=False, use_extra_body= set_null_values=set_null_values, use_extra_body=use_extra_body, ) - else: - return {} + return {} def create_chat_completions(self, messages, model_name=None, timeout=None): model = model_name if model_name is not None else self.api_type.model.name @@ -529,7 +528,7 @@ def create_rerank(self, rerank_input, model_name=None): return rerank -class InferenceResponse(object): +class InferenceResponse: def __init__(self, inference_info, response): self.inference_info = inference_info @@ -541,9 +540,8 @@ def create(cls, inference_info, response): def ensure_outputs_exist(self): for output_name in self.inference_info.model.outputs: - assert output_name in self.response, "Incorrect output name, expected: {}, found: {}.".format( - output_name, ", ".join(self.response.keys()) - ) + assert output_name in self.response, f"Incorrect output name, expected: {output_name}, " \ + f"found: {', '.join(self.response.keys())}." def validate(self, input_data): self.ensure_outputs_exist() @@ -582,15 +580,12 @@ def validate_expected_shape(self, response): if expected_dim_value > 0: validation_pass = expected_dim_value == output_shape[name][dim] - assert validation_pass, "Incorrect output shape, expected: {}, found: {}.".format(expected_shape, output_shape) + assert validation_pass, f"Incorrect output shape, expected: {expected_shape}, found: {output_shape}." logger.debug(f"Output shape: {output_shape} (expected: {expected_shape})") class MediaPipeInferenceResponse(InferenceResponse): - def __init__(self, inference_info, response): - super().__init__(inference_info, response) - @classmethod def create(cls, inference_info, response): return MediaPipeInferenceResponse(inference_info, response) @@ -629,15 +624,12 @@ def validate_expected_shape(self, response, output_key=None): output_shape_key = f"out_{expected_shape_idx}" validation_pass = expected_dim_value == output_shape[output_shape_key][dim] - assert validation_pass, "Incorrect output shape, expected: {}, found: {}.".format(expected_shape, output_shape) + assert validation_pass, f"Incorrect output shape, expected: {expected_shape}, found: {output_shape}." logger.debug(f"Output shape: {output_shape} (expected: {expected_shape})") class LLMInferenceResponse(InferenceResponse): - def __init__(self, inference_info, response): - super().__init__(inference_info, response) - @classmethod def create(cls, inference_info, response): return LLMInferenceResponse(inference_info, response) @@ -649,7 +641,7 @@ def validate(self): assert self.response["model"] == self.inference_info.model.name, f"Invalid model name: {self.response['model']}" -class InferenceInfo(object): +class InferenceInfo: @classmethod def create(cls, client, model, timeout=wait_for_messages_timeout, input_data=None, inference_request=None): @@ -911,8 +903,7 @@ def prepare_v2_model_infer_request(port, api_type, input_data=None): grpc_stub = prepare_v2_grpc_stub(port) request = api_type.get_predict_grpc_request(input_data) return request, grpc_stub - else: - raise NotImplementedError() + raise NotImplementedError() def check_model_readiness(model, port, kfs_api_type, is_ready=True, timeout=None): @@ -929,7 +920,7 @@ def check_model_readiness(model, port, kfs_api_type, is_ready=True, timeout=None logger.info(f"Model {model.name} Ready:\n{response}") success = True break - elif not response and not is_ready: + if not response and not is_ready: logger.info(f"Model {model.name} is not Ready: {response}") success = True break @@ -1299,7 +1290,7 @@ def decode_result(result, output_name, results_decoded): if error: raise error - elif any(result.as_numpy(output_name) is not None for output_name in mediapipe_model.output_names): + if any(result.as_numpy(output_name) is not None for output_name in mediapipe_model.output_names): for output_name in mediapipe_model.output_names: if result.as_numpy(output_name) is not None: decode_result(result, output_name, results_decoded) diff --git a/tests/functional/object_model/mediapipe_calculators.py b/tests/functional/object_model/mediapipe_calculators.py index f8696e6fce..c47befbb63 100644 --- a/tests/functional/object_model/mediapipe_calculators.py +++ b/tests/functional/object_model/mediapipe_calculators.py @@ -63,7 +63,7 @@ def prepare_proto_calculator(cls, parameters, config_path_on_host, config_file=N config_data = ( parameters.custom_config if parameters.custom_config is not None - else json.loads(Path(config_file).read_text()) if config_file is not None else {} + else json.loads(Path(config_file).read_text(encoding="utf-8")) if config_file is not None else {} ) for mediapipe_model in mediapipe_models: dst_path = os.path.join(config_path_on_host, mediapipe_model.name) if config_path_on_host is not None \ @@ -77,7 +77,8 @@ def prepare_proto_calculator(cls, parameters, config_path_on_host, config_file=N real_path = os.path.expanduser(calc) real_path = os.path.realpath(real_path) logger.info( - "Copy custom calculator file to {}, content:\n{}".format(dst_path, Path(real_path).read_text()) + f"Copy custom calculator file to {dst_path}, " + f"content:\n{Path(real_path).read_text(encoding='utf-8')}" ) Path(dst_path).mkdir(parents=True, exist_ok=True) shutil.copy(real_path, dst_path) @@ -224,14 +225,15 @@ def save( content = cls.get_full_content(content, model, input_stream, output_stream) Path(dst_path).mkdir(parents=True, exist_ok=True) file_path = os.path.join(dst_path, filename) - with open(file_path, "w+") as f: + with open(file_path, "w+", encoding="utf-8") as f: f.write(content) - logger.info(f"Saving calculator file to {file_path}, content:\n{content}") + logger.info(f"Saving calculator file to {file_path}, " + f"content:\n{content}") return file_path @staticmethod def load(filepath): - with open(filepath, "r") as f: + with open(filepath, "r", encoding="utf-8") as f: data = f.read() return data @@ -630,14 +632,14 @@ def get_plugin_config_params_list(plugin_config_dict): plugin_config_params = [] for plugin_config_key, plugin_config_value in plugin_config_dict.items(): if plugin_config_value is not None: - if type(plugin_config_value) == int: + if isinstance(plugin_config_value, int): plugin_config_params.append(f'"{plugin_config_key}": {plugin_config_value}') - elif type(plugin_config_value) == bool: + elif isinstance(plugin_config_value, bool): plugin_config_value = "true" if plugin_config_value else "false" plugin_config_params.append(f'"{plugin_config_key}": {plugin_config_value}') - elif type(plugin_config_value) == str: + elif isinstance(plugin_config_value, str): plugin_config_params.append(f'"{plugin_config_key}": "{plugin_config_value}"') - elif type(plugin_config_value) == dict: + elif isinstance(plugin_config_value, dict): plugin_config_params_dict = get_plugin_config_params_list(plugin_config_value) plugin_config_params_dict_str = ', '.join(plugin_config_params_dict) plugin_config_params.append(f"\"{plugin_config_key}\": {{ {plugin_config_params_dict_str} }}") @@ -676,7 +678,7 @@ def get_plugin_config_params_list(plugin_config_dict): enable_prefix_caching_str = "" if enable_prefix_caching_config: - enable_prefix_caching_str = f"enable_prefix_caching: true" + enable_prefix_caching_str = "enable_prefix_caching: true" tool_guided_str = "" if self.model.enable_tool_guided_generation and self.enable_tool_guided_generation: diff --git a/tests/functional/object_model/ovms_binary.py b/tests/functional/object_model/ovms_binary.py index 1d4ef610ac..2bad42794d 100644 --- a/tests/functional/object_model/ovms_binary.py +++ b/tests/functional/object_model/ovms_binary.py @@ -364,7 +364,7 @@ def update_model_list_and_config( else: OvmsConfig.generate(name, models) - config_dict = json.loads(Path(config_path_on_host).read_text()) + config_dict = json.loads(Path(config_path_on_host).read_text(encoding="utf-8")) if models_to_verify: break_msg_list = self.get_break_msg_list(models_to_verify) diff --git a/tests/functional/object_model/ovms_capi.py b/tests/functional/object_model/ovms_capi.py index 6726295155..f3b368ca10 100644 --- a/tests/functional/object_model/ovms_capi.py +++ b/tests/functional/object_model/ovms_capi.py @@ -98,11 +98,10 @@ def __init__(self, parameters, base_os, **kwargs): def get_port(self, api_type): if not isinstance(api_type, str) and api_type.communication == OvmsType.CAPI: return self - else: - return super().get_port(api_type) + return super().get_port(api_type) def get_status(self, status=None, timeout=None): - status = Path(f"/proc/{self.process._proc.pid}/status").read_text() + status = Path(f"/proc/{self.process._proc.pid}/status").read_text(encoding="utf-8") status = [line for line in status.splitlines() if line.startswith("State:")] if "sleeping" in status[0]: result = CONTAINER_STATUS_RUNNING @@ -163,7 +162,7 @@ def send_command_to_process(self, cmd): # Write directly to stdin file descriptor in /proc/ filesystem. # It should mitigate interprocess communication issues in 'pure pythonic' approach. logger.debug(self._stdin_proc_fd) - with open(self._stdin_proc_fd, "w") as fd: + with open(self._stdin_proc_fd, "w", encoding="utf-8") as fd: fd.write(self.ensure_newline(cmd)) def send_command_to_process_with_output(self, cmd, cmd_kwargs): @@ -190,19 +189,19 @@ def send_start_server_command(self): return self.send_command_to_process(cmd) def send_stop_server_command(self): - cmd = f"self.srv = self.capi.server_stop()" + cmd = "self.srv = self.capi.server_stop()" return self.send_command_to_process(cmd) def send_terminate_command(self): - cmd = f"self.running = False" + cmd = "self.running = False" return self.send_command_to_process(cmd) def send_terminate_command(self): - cmd = f"self.running = False" + cmd = "self.running = False" return self.send_command_to_process(cmd) def send_get_model_meta_command(self, model_name, model_version): - cmd = f"self.capi.get_model_meta()" + cmd = "self.capi.get_model_meta()" cmd_kwargs = {"servableName": model_name, "servableVersion": model_version} return self.send_command_to_process_with_output(cmd, cmd_kwargs) @@ -216,7 +215,7 @@ def send_inference(self, model, input_data): _in_data = {key: value.shape for key, value in input_data.items()} # Write directly to stdin file descriptor in /proc/ filesystem. # It should mitigate interprocess communication issues in 'pure pythonic' approach. - cmd = f"self.result = self.capi.send_inference()" + cmd = "self.result = self.capi.send_inference()" cmd_kwargs = { "model_name": model.name, "inputs": input_data, @@ -233,7 +232,7 @@ def send_get_capi_api_version(self): def get_major_minor_version(self): filepath = os.path.join(ovms_c_repo_path, "src/ovms.h") - with open(filepath, "r") as f: + with open(filepath, "r", encoding="utf-8") as f: data = f.read() major = re.search(r"OVMS_API_VERSION_MAJOR (\d+)", data).group(1) diff --git a/tests/functional/object_model/ovms_command.py b/tests/functional/object_model/ovms_command.py index 35f52281e4..cfc3d3195e 100644 --- a/tests/functional/object_model/ovms_command.py +++ b/tests/functional/object_model/ovms_command.py @@ -92,32 +92,31 @@ def create_ovms_command( if add_to_config or remove_from_config: return OvmsCommand(config_path=config_path, model_name=model_name, **common_parameters, **pull_parameters) return OvmsCommand(config_path=config_path, **common_parameters) - else: - plugin_config = parameters.get_plugin_config_from_regular_models() - if enable_plugin_config_target_device: - plugin_config_target_device = Ovms.PLUGIN_CONFIG[get_base_device(parameters.target_device)] - plugin_config = ( - {**plugin_config, **plugin_config_target_device} - if plugin_config is not None - else {**plugin_config_target_device} - ) - use_parameter = not any([single_mediapipe_model_mode, list_models, add_to_config, remove_from_config]) - return OvmsCommand( - model_path=model_path, - model_name=model_name, - plugin_config=plugin_config if use_parameter else None, - batchsize=batch_size if use_parameter else None, - nireq=parameters.nireq if use_parameter else None, - target_device=parameters.target_device if use_parameter else None, - shape=shape if use_parameter else None, - model_version_policy=parameters.model_version_policy if use_parameter else None, - **common_parameters, - **pull_parameters, + plugin_config = parameters.get_plugin_config_from_regular_models() + if enable_plugin_config_target_device: + plugin_config_target_device = Ovms.PLUGIN_CONFIG[get_base_device(parameters.target_device)] + plugin_config = ( + {**plugin_config, **plugin_config_target_device} + if plugin_config is not None + else {**plugin_config_target_device} ) + use_parameter = not any([single_mediapipe_model_mode, list_models, add_to_config, remove_from_config]) + return OvmsCommand( + model_path=model_path, + model_name=model_name, + plugin_config=plugin_config if use_parameter else None, + batchsize=batch_size if use_parameter else None, + nireq=parameters.nireq if use_parameter else None, + target_device=parameters.target_device if use_parameter else None, + shape=shape if use_parameter else None, + model_version_policy=parameters.model_version_policy if use_parameter else None, + **common_parameters, + **pull_parameters, + ) @dataclass -class OvmsCommand(object): +class OvmsCommand: logging_level: str = None model_path: str = None model_name: str = None diff --git a/tests/functional/object_model/ovms_config.py b/tests/functional/object_model/ovms_config.py index 8595d0771b..277403195c 100644 --- a/tests/functional/object_model/ovms_config.py +++ b/tests/functional/object_model/ovms_config.py @@ -37,7 +37,7 @@ logger = get_logger(__name__) -class OvmsConfig(object): +class OvmsConfig: @staticmethod def generate(name, models, **kwargs): @@ -104,19 +104,19 @@ def save(name, config_dict: dict, config_path: str = None): if config_path is None else config_path ) - logger.info("Saving config file to {}, content:\n{}".format(config_path, config_json)) + logger.info(f"Saving config file to {config_path}, content:\n{config_json}") os.makedirs(os.path.dirname(config_path), exist_ok=True) - with open(os.path.join(config_path), "w") as outfile: + with open(os.path.join(config_path), "w", encoding="utf-8") as outfile: outfile.write(config_json) return Paths.CONFIG_PATH_INTERNAL @staticmethod def save_without_encoding(config_path, config_dict: dict): - logger.info("Saving config file to {}, content:\n{}".format(config_path, str(config_dict))) + logger.info(f"Saving config file to {config_path}, content:\n{str(config_dict)}") os.makedirs(os.path.dirname(config_path), exist_ok=True) - with open(os.path.join(config_path), "w") as outfile: + with open(os.path.join(config_path), "w", encoding="utf-8") as outfile: outfile.write(str(config_dict)) return Paths.CONFIG_PATH_INTERNAL @@ -232,12 +232,12 @@ def build_ovms_config( @staticmethod def load(config_path): - with open(config_path, "r") as f: + with open(config_path, "r", encoding="utf-8") as f: try: config_json = f.read() config_dict = json.loads(config_json) except ValueError as e: - logger.error("Error while loading json: {}".format(config_json)) + logger.error(f"Error while loading json: {config_json}") raise e return config_dict @@ -295,7 +295,7 @@ def replace_config_models_paths_for_binary(context, config_path, resources_dir, @staticmethod def replace_subconfig_paths(name, subconfig_path, resources_dir): - subconfig_dict = json.loads(Path(subconfig_path).read_text()) + subconfig_dict = json.loads(Path(subconfig_path).read_text(encoding="utf-8")) for i, model in enumerate(subconfig_dict["model_config_list"]): subconfig_dict["model_config_list"][i]["config"]["base_path"] = model["config"]["base_path"].replace( Paths.MODELS_PATH_INTERNAL, os.path.join(resources_dir, Paths.MODELS_PATH_NAME) @@ -311,7 +311,7 @@ def create_subconfig(name, parameters, config_path_on_host): else: config_path = Path(os.path.join(config_path_on_host, Paths.CONFIG_FILE_NAME)) if config_path.exists(): - config_dict = json.loads(config_path.read_text()) + config_dict = json.loads(config_path.read_text(encoding="utf-8")) subconfig_dict = {Config.MODEL_CONFIG_LIST: []} mediapipe_model = [model for model in parameters.models if model.is_mediapipe][0] diff --git a/tests/functional/object_model/ovms_docker.py b/tests/functional/object_model/ovms_docker.py index 6edd3ff1e9..1cd09d5444 100644 --- a/tests/functional/object_model/ovms_docker.py +++ b/tests/functional/object_model/ovms_docker.py @@ -69,7 +69,7 @@ class OvmsDockerParams(OvmsParams): network: str = None -class OvmsDockerLauncher(object): +class OvmsDockerLauncher: @classmethod def _update_ports(cls, context, port, parameters, ovms_instance_params, full_cmd): @@ -114,7 +114,7 @@ def create( ): if parameters.models is not None: logger.info( - "Creating ovms with model(s): {}".format(", ".join([model.name for model in parameters.models])) + f"Creating ovms with model(s): {', '.join([model.name for model in parameters.models])}" ) if parameters.name is None: parameters.name = ( @@ -198,7 +198,7 @@ def create( @classmethod def _prepare_ports(cls, grpc_port: int = None, rest_port: int = None) -> dict: - ports = dict() + ports = {} if grpc_port is not None: ports.update({f"{grpc_port}/tcp": grpc_port}) if rest_port is not None: @@ -251,7 +251,7 @@ def build_ovms_instance_params(cls, context: Context, parameters: OvmsDockerPara if parameters.volumes is None: volumes.update(cls.prepare_new_volumes_for_container([config_path_on_host])) config_file = os.path.join(config_path_on_host, Paths.CONFIG_FILE_NAME) - config_data = Path(config_file).read_text() + config_data = Path(config_file).read_text(encoding="utf-8") # Create and save .pbtxt file for each model in pipeline if ( @@ -378,15 +378,10 @@ def build_ovms_instance_params(cls, context: Context, parameters: OvmsDockerPara else: command = parameters.custom_command - docker_kwargs = dict(volumes=volumes, devices=devices, network=network, privileged=privileged) + docker_kwargs = {"volumes": volumes, "devices": devices, "network": network, "privileged": privileged} docker_kwargs.update(extra_docker_params) - instance_kwargs = dict( - container_folder=container_folder, - rest_port=parameters.rest_port, - grpc_port=parameters.grpc_port, - target_device=parameters.target_device, - ) + instance_kwargs = {"container_folder": container_folder, "rest_port": parameters.rest_port, "grpc_port": parameters.grpc_port, "target_device": parameters.target_device} if parameters.limits: docker_kwargs.update(parameters.limits) @@ -409,7 +404,7 @@ def create_config(cls, parameters, name): regular_models = parameters.get_regular_models() using_custom_loader = any([(x.custom_loader is not None) for x in regular_models]) if using_custom_loader and not parameters.use_config: - msg = f"Custom loader is supported only with config file passed with --config_path." + msg = "Custom loader is supported only with config file passed with --config_path." logger.error(msg) raise Exception(msg) @@ -436,8 +431,7 @@ def create_config(cls, parameters, name): if use_config: return config_dir_path_on_host, Paths.CONFIG_PATH_INTERNAL - else: - return None, None + return None, None @staticmethod def prepare_new_volumes_for_container(container_folders, mode="ro"): @@ -485,7 +479,7 @@ def update_volume_for_mounts(mounts: list, volumes: dict): def parse_cmd(result, environment, entrypoint, entrypoint_params): - full_cmd = f"docker run -d" + full_cmd = "docker run -d" if result["docker_kwargs"]["privileged"]: full_cmd += " --privileged" @@ -528,7 +522,7 @@ def parse_cmd(result, environment, entrypoint, entrypoint_params): # '/ovms/bin/ovms --log_level INFO --port 9007 --rest_port 8005 --config_path /models/config.json' if entrypoint is not None: - full_cmd += " " + result["command"] if type(result["command"]) == str else " " + " ".join(result["command"]) + full_cmd += " " + result["command"] if isinstance(result["command"], str) else " " + " ".join(result["command"]) elif "/ovms/bin/ovms" in result["command"]: result["command"] = result["command"].partition("/ovms/bin/ovms")[2].strip() full_cmd += " " + result["command"] @@ -691,7 +685,7 @@ def remove_container(self, ensure_deleted: bool = False): _, stdout, _ = process.run(f"docker ps --filter id={self.get_short_id()}") if short_id not in stdout: break - elif time.time() > timeout: + if time.time() > timeout: raise TimeoutError(f"Container {short_id} is not removed") def execute_command(self, cmd, stream=False, cwd=None): diff --git a/tests/functional/object_model/ovms_info.py b/tests/functional/object_model/ovms_info.py index 8ad621b103..08fe0c01b7 100644 --- a/tests/functional/object_model/ovms_info.py +++ b/tests/functional/object_model/ovms_info.py @@ -222,7 +222,7 @@ def pull_latest_image(cls, image_to_pull, force_pull=False): if image_to_pull not in cls.IMAGES or force_pull: repository, tag = image_to_pull.split(":") - logger.info("Pulling image: {} tag: {}".format(repository, tag)) + logger.info(f"Pulling image: {repository} tag: {tag}") image = DockerClient().pull(repository=repository, tag=tag) cls.IMAGES[image_to_pull] = image return cls.IMAGES[image_to_pull] diff --git a/tests/functional/object_model/ovms_instance.py b/tests/functional/object_model/ovms_instance.py index f3c1d4b2b2..66991d3161 100644 --- a/tests/functional/object_model/ovms_instance.py +++ b/tests/functional/object_model/ovms_instance.py @@ -257,8 +257,7 @@ def unload_all_models(self): def get_port(self, api_type): if isinstance(api_type, str): return self.ovms_ports[api_type] - else: - return self.ovms_ports[api_type.type] + return self.ovms_ports[api_type.type] def execute_and_check(self, cmd, verbose=False, cwd=None): exit_code, stdout = self.execute_command(cmd, cwd) @@ -326,8 +325,7 @@ def wait_for_status(self, status: str = CONTAINER_STATUS_RUNNING, break_status: if result: exception, line = result raise exception(line) - else: - raise OvmsTestException(f"Received break status: {current_status}", ovms_log=ovms_logs_lines) + raise OvmsTestException(f"Received break status: {current_status}", ovms_log=ovms_logs_lines) if current_status == status: break @@ -391,12 +389,11 @@ def _create_logger(self) -> LogMonitor: def get_signal_type(terminate_signal_type): if terminate_signal_type == Ovms.SIGKILL_SIGNAL: return signal.SIGKILL - elif terminate_signal_type == Ovms.SIGINT_SIGNAL: + if terminate_signal_type == Ovms.SIGINT_SIGNAL: return signal.SIGINT - elif terminate_signal_type == Ovms.SIGTERM_SIGNAL: + if terminate_signal_type == Ovms.SIGTERM_SIGNAL: return signal.SIGTERM - else: - raise NotImplementedError(f"Unknown signal: {terminate_signal_type}") + raise NotImplementedError(f"Unknown signal: {terminate_signal_type}") def filter_unexpected_messages(self, unexpected_messages): for msg in unexpected_messages: diff --git a/tests/functional/object_model/ovms_params.py b/tests/functional/object_model/ovms_params.py index e732516f4b..9d5017508a 100644 --- a/tests/functional/object_model/ovms_params.py +++ b/tests/functional/object_model/ovms_params.py @@ -34,7 +34,7 @@ @dataclass_json @dataclass(frozen=False) -class OvmsParams(object): +class OvmsParams: name: str = None grpc_port: int = None log_level: str = logging_level_ovms @@ -97,7 +97,7 @@ def get_regular_models(self): result = [] if self.list_models or self.add_to_config or self.remove_from_config: return result - elif self.model_name is not None: + if self.model_name is not None: result.append(ModelsLib.create_model(self.model_name)) elif self.models is None: result.append(ModelsLib.get_default_model(self.target_device)()) diff --git a/tests/functional/object_model/package_manager.py b/tests/functional/object_model/package_manager.py index a0f00c3a7b..9bdfceece1 100644 --- a/tests/functional/object_model/package_manager.py +++ b/tests/functional/object_model/package_manager.py @@ -45,7 +45,7 @@ def __init__(self): def create(base_os=OsType.Ubuntu24): if OsType.Redhat in base_os: return MicrodnfPackageManager() - elif OsType.Ubuntu22 in base_os or OsType.Ubuntu24 in base_os: + if OsType.Ubuntu22 in base_os or OsType.Ubuntu24 in base_os: return AptPackageManager() raise NotImplementedError() @@ -107,8 +107,7 @@ def install_missing_packages_on_host(self, container_pkg_list, host_pkg_list, mi after_install_host_pkg_list = self.get_list_of_installed_packages(container_id=None) assert not self.get_missing_packages(container_pkg_list, after_install_host_pkg_list) return host_pkg_list - else: # Return if there are no more pkgs_to_install except those defined in GPU_LIBS_TO_SKIP - return host_pkg_list + return host_pkg_list # Return if there are no more pkgs_to_install except those defined in GPU_LIBS_TO_SKIP def upgrade_packages(self, packages_to_upgrade, container_pkg_list): for key, value in packages_to_upgrade.items(): @@ -119,7 +118,7 @@ def upgrade_packages(self, packages_to_upgrade, container_pkg_list): except InstallPkgVersionException as e: cmd, retcode, stdout, stderr = e.get_process_details() if "The following packages have unmet dependencies" in stdout: - logger.debug(f"Upgrading all system packages ...") + logger.debug("Upgrading all system packages ...") self.run_process(self.upgrade_cmd, exception_type=UpgradePkgException) host_packages = self.get_list_of_installed_packages(container_id=None) if not self.get_packages_to_upgrade(host_packages, container_pkg_list): @@ -134,11 +133,10 @@ def upgrade_packages(self, packages_to_upgrade, container_pkg_list): pkgs = self.get_packages_to_upgrade(host_packages, container_pkg_list) if not pkgs: return - else: - for key, value in pkgs.items(): - logger.warning( - f"Failed to upgrade package {key}. Continue with the current version: {value['version']}" - ) + for key, value in pkgs.items(): + logger.warning( + f"Failed to upgrade package {key}. Continue with the current version: {value['version']}" + ) class DnfPackageManager(PackageManager): diff --git a/tests/functional/object_model/resource_monitor.py b/tests/functional/object_model/resource_monitor.py index ce0833785c..8743803b72 100644 --- a/tests/functional/object_model/resource_monitor.py +++ b/tests/functional/object_model/resource_monitor.py @@ -77,15 +77,9 @@ class DockerResourceMonitor(ResourceMonitor): FIELDS_TO_STATS = { "DATE": lambda x: x["read"], "PIDS_COUNT": lambda x: int(x["pids_stats"].get("current", "0")), - MEMORY_USAGE: lambda x: "{:.2f}M".format(float(x["memory_stats"].get("usage", "0.0")) / (2**20)), - PRIVATE_MEMORY: lambda x: "{:.2f}M".format( - float(x["memory_stats"].get("stats", {}).get( - "anon", x["memory_stats"].get("stats", {}).get("rss", 0) - )) / (2**20) - ), - MEMORY_CACHE: lambda x: "{:.2f}M".format( - _cgroup_cache_bytes(x["memory_stats"].get("stats", {})) / (2**20) - ), + MEMORY_USAGE: lambda x: f"{float(x['memory_stats'].get('usage', '0.0')) / (2**20):.2f}M", + PRIVATE_MEMORY: lambda x: f"{float(x['memory_stats'].get('stats', {}).get('anon', x['memory_stats'].get('stats', {}).get('rss', 0))) / (2**20):.2f}M", # pylint: disable=line-too-long + MEMORY_CACHE: lambda x: f"{_cgroup_cache_bytes(x['memory_stats'].get('stats', {})) / (2**20):.2f}M", # Enable after debug & fixing # "CPU_USAGE": lambda x: # [cpu / x['cpu_stats']['cpu_usage']['total_usage'] for cpu in x['cpu_stats']['cpu_usage']['percpu_usage']], @@ -118,7 +112,7 @@ def save_data(self): row[field] = self.get_field_data(field, stats) self.rows.append(row) log_path = Path(artifacts_dir, f"docker_stats_{self.container.name}.log") - with log_path.open("w") as csvfile: + with log_path.open("w", encoding="utf-8") as csvfile: writer = csv.DictWriter(csvfile, fieldnames=DockerResourceMonitor.FIELDS) writer.writeheader() writer.writerows(self.rows) @@ -140,7 +134,7 @@ def save_diagrams(self): x = np.array([(parser.parse(x["DATE"]) - started).seconds for x in self.rows]) for field in DockerResourceMonitor.FIELDS[1:]: y = np.array([x[field] for x in self.rows]) - filename = "diagram_{}_{}.png".format(field, self.container.name) + filename = f"diagram_{field}_{self.container.name}.png" self.plot_fo_file(x, y, field, filename) def check_resources(self): @@ -263,7 +257,7 @@ def check_resources(self): def save_data(self): self.rows = list(self._stats_data_raw) log_path = Path(artifacts_dir, f"windows_stats_pid_{self.ovms_pid}.log") - with log_path.open("w") as csvfile: + with log_path.open("w", encoding="utf-8") as csvfile: writer = csv.DictWriter(csvfile, fieldnames=self.FIELDS) writer.writeheader() writer.writerows(self.rows) diff --git a/tests/functional/object_model/test_environment.py b/tests/functional/object_model/test_environment.py index c6663a51e4..be68af5e39 100644 --- a/tests/functional/object_model/test_environment.py +++ b/tests/functional/object_model/test_environment.py @@ -25,7 +25,7 @@ logger = get_logger(__name__) -class TestEnvironment(object): +class TestEnvironment: __test__ = False current = None @@ -37,10 +37,10 @@ def update_model_files(model, models_dir): if hasattr(model, "max_position_embeddings") and model.max_position_embeddings is not None: config_file_path = os.path.join(models_dir[0], model.name, "config.json") if os.path.exists(config_file_path): - with open(config_file_path, "r") as file_object: + with open(config_file_path, "r", encoding="utf-8") as file_object: config_data = json.load(file_object) config_data["max_position_embeddings"] = model.max_position_embeddings - with open(config_file_path, "w") as file_object: + with open(config_file_path, "w", encoding="utf-8") as file_object: json.dump(config_data, file_object) logger.info( f"max_position_embeddings value was updated to {model.max_position_embeddings} " diff --git a/tests/functional/object_model/test_helpers.py b/tests/functional/object_model/test_helpers.py index dc62fac700..677a1411ee 100644 --- a/tests/functional/object_model/test_helpers.py +++ b/tests/functional/object_model/test_helpers.py @@ -110,7 +110,7 @@ def send_request_to_endpoint(port, address=None, endpoint=None, expected_code=No endpoint == Endpoints.RELOAD_CONFIG.value]): logger.warning(f"{msg1} {msg2} Both of those codes are accepted.") return ret - elif not ret.status_code == expected_code: + if not ret.status_code == expected_code: raise InvalidReturnCodeException(f"{msg1} {msg2}") logger.info(msg1) return ret diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc index 90c83a19e1..68ac8f499b 100644 --- a/tests/functional/pylintrc +++ b/tests/functional/pylintrc @@ -535,6 +535,7 @@ disable= missing-class-docstring, # (C0115, to be FIXED) missing-module-docstring, # (C0114, to be FIXED) too-few-public-methods, # (R0903, to be FIXED) + too-many-instance-attributes # (allowed intentionally) too-many-lines, # (allowed intentionally) too-many-statements, # (allowed intentionally) too-many-locals, # (allowed intentionally) diff --git a/tests/functional/utils/assertions.py b/tests/functional/utils/assertions.py index 15c3a0477c..a6aa23f721 100644 --- a/tests/functional/utils/assertions.py +++ b/tests/functional/utils/assertions.py @@ -103,7 +103,7 @@ def get_mediapipe_details_from_context(context): log_monitor = ovms_session.ovms.create_log(True) ovms_log = log_monitor.get_logs_as_txt() config_file = os.path.join(ovms_session.ovms.container_folder, Paths.MODELS_PATH_NAME, Paths.CONFIG_FILE_NAME) - config = json.loads(Path(config_file).read_text()) + config = json.loads(Path(config_file).read_text(encoding="utf-8")) mediapipe_model = [model for model in ovms_session.models if model.is_mediapipe][0] src_code = [calc.src_file_path for calc in mediapipe_model.calculators] graphs = mediapipe_model.graphs diff --git a/tests/functional/utils/context.py b/tests/functional/utils/context.py index 5af8b4a680..d0fa6299ca 100644 --- a/tests/functional/utils/context.py +++ b/tests/functional/utils/context.py @@ -25,7 +25,7 @@ from tests.functional.utils.logger import get_logger -class Context(object): +class Context: logger = get_logger("context") EXCEPTIONS_TO_CATCH = [ UnexpectedResponseError, diff --git a/tests/functional/utils/core.py b/tests/functional/utils/core.py index 715c7dcbae..46ec8edfe9 100644 --- a/tests/functional/utils/core.py +++ b/tests/functional/utils/core.py @@ -46,7 +46,7 @@ def get_children_from_module(parent, module): def get_token_value(token_file_path, fallback_value=None): if os.path.exists(token_file_path): - token_value = Path(token_file_path).read_text().strip() + token_value = Path(token_file_path).read_text(encoding="utf-8").strip() return token_value return fallback_value @@ -160,7 +160,6 @@ class ComplexEncoder(json.JSONEncoder): def default(self, obj): if hasattr(obj, 'to_str'): return obj.to_str() - elif isinstance(obj, type): + if isinstance(obj, type): return str(obj) - else: - return json.JSONEncoder.default(self, obj) + return json.JSONEncoder.default(self, obj) diff --git a/tests/functional/utils/docker.py b/tests/functional/utils/docker.py index 35d5a472b0..b375c302f4 100644 --- a/tests/functional/utils/docker.py +++ b/tests/functional/utils/docker.py @@ -42,7 +42,7 @@ class DockerClient(docker.DockerClient): def build(self, dockerfile: str, build_args, nocache: bool = True, **kwargs) -> tuple: logs = [] - with open(dockerfile, "r") as file: + with open(dockerfile, "r", encoding="utf-8") as file: data = file.read() file_obj = BytesIO(data.encode("utf-8")) image, generator = self.images.build(fileobj=file_obj, nocache=nocache, buildargs=build_args, **kwargs) @@ -61,8 +61,8 @@ def push(self, repository, tag=None, **kwargs): for line in logs: assert ( "requested access to the resource is denied" not in line - ), "Unauthorized to push docker image: {}".format(line) - assert "error" not in line, "Failed to push docker image: {}".format(line) + ), f"Unauthorized to push docker image: {line}" + assert "error" not in line, f"Failed to push docker image: {line}" return logs def pull(self, repository, tag): @@ -154,7 +154,7 @@ def run( limits: Limits = None, **kwargs, ): - logger.info("Running container with:\n image: {}\n command: {}\n volumes: {}".format(image, command, volumes)) + logger.info(f"Running container with:\n image: {image}\n command: {command}\n volumes: {volumes}") if limits is not None: kwargs.update(limits) container = cls.client.run( @@ -190,7 +190,7 @@ def create( limits: Limits = None, **kwargs, ): - logger.info("Creating container with:\n image: {}\n command: {}\n volumes: {}".format(image, command, volumes)) + logger.info(f"Creating container with:\n image: {image}\n command: {command}\n volumes: {volumes}") if limits is not None: kwargs.update(limits) container = cls.client.create( @@ -249,32 +249,28 @@ def container_name(cls, container_name: str = None): @classmethod def volume(cls, external_path: str, internal_path: str, mode: str = "ro", volumes: dict = None): if not isinstance(volumes, dict): - volumes = dict() + volumes = {} volumes[external_path] = {"bind": internal_path, "mode": mode} return volumes def start_container(self): - assert self.container is not None, "Lack of container {} to start (is None)\nContainers found:\n{}".format( - self.name, repr(self.client.list_containers(all_containers=True)) - ) + assert self.container is not None, f"Lack of container {self.name} to start (is None)\n" \ + f"Containers found:\n{repr(self.client.list_containers(all_containers=True))}" return self.container.start() def stop_container(self, **kwargs): - assert self.container is not None, "Lack of container {} to stop (is None)\nContainers found:\n{}".format( - self.name, repr(self.client.list_containers(all_containers=True)) - ) + assert self.container is not None, f"Lack of container {self.name} to stop (is None)\n" \ + f"Containers found:\n{repr(self.client.list_containers(all_containers=True))}" return self.container.stop(**kwargs) def kill_container(self, signal=signal.SIGTERM): - assert self.container is not None, "Lack of container {} to kill (is None)\nContainers found:\n{}".format( - self.name, repr(self.client.list_containers(all_containers=True)) - ) + assert self.container is not None, f"Lack of container {self.name} to kill (is None)\n" \ + f"Containers found:\n{repr(self.client.list_containers(all_containers=True))}" return self.container.kill(signal=signal) # SIGKILL (not supported for Windows), SIGINT; default: SIGTERM def remove_container(self, ensure_deleted: bool = False): - assert self.container is not None, "Lack of container {} to remove (is None)\nContainers found:\n{}".format( - self.name, repr(self.client.list_containers(all_containers=True)) - ) + assert self.container is not None, f"Lack of container {self.name} to remove (is None)\n" \ + f"Containers found:\n{repr(self.client.list_containers(all_containers=True))}" removed = self.container.remove() if ensure_deleted: self.ensure_not_on_list(self.name) @@ -286,8 +282,8 @@ def delete(self, ensure_deleted: bool = False): def check_non_empty_logs(self, specific_str: str, acceptable_logs_length_trigger: int = 0, **kwargs): logs = self.get_logs(**kwargs) - assert len(logs) > acceptable_logs_length_trigger, "Logs list for {} should not be empty".format(self.name) - assert specific_str in logs, "Specific string: {} not found in logs: {}".format(specific_str, logs) + assert len(logs) > acceptable_logs_length_trigger, f"Logs list for {self.name} should not be empty" + assert specific_str in logs, f"Specific string: {specific_str} not found in logs: {logs}" return logs def ensure_logs_contain_specific_str( @@ -356,18 +352,15 @@ def get_logs(self, **kwargs) -> Union[bool, str]: def check_not_on_list(cls, container: Union[str, "DockerContainer"], comparator: Callable[[Any, Any], bool] = None): current_list = cls.list() logger.debug( - "Searching for container with a name: {name}, among:\n{elem}\n".format( - name=container if isinstance(container, str) else container.name, - elem="\n".join([repr(elem) for elem in current_list]), - ) + f"Searching for container with a name: {container if isinstance(container, str) else container.name}, " + f"among:\n{'\n'.join([repr(elem) for elem in current_list])}\n" ) if comparator is None: - assert container not in current_list, "{} was found on: {}".format(container, pprint.pformat(current_list)) + assert container not in current_list, f"{container} was found on: {pprint.pformat(current_list)}" else: for member in current_list: - assert comparator(container, member) is False, "{} was found on: {}".format( - container, pprint.pformat(current_list) - ) + assert comparator(container, member) is False, \ + f"{container} was found on: {pprint.pformat(current_list)}" @classmethod def ensure_not_on_list( diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index d64f089297..85fceece97 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -220,7 +220,7 @@ def cleanup_tmp_repos_dir(config): try: shutil.rmtree(config.tmp_repos_dir) except PermissionError as e: - if get_host_os() == OsType.Windows and type(e) == PermissionError: + if get_host_os() == OsType.Windows and isinstance(e, PermissionError): change_dir_permissions(config.tmp_repos_dir) shutil.rmtree(config.tmp_repos_dir) @@ -409,7 +409,7 @@ def build_ovms_capi_image(): shutil.copy(os.path.join(ovms_c_repo_path, gpu_install_script), ovms_capi_dst_path) else: for gpu_install_script in GPU_INSTALL_SCRIPTS[base_os]: - with open(os.path.join(ovms_capi_dst_path, gpu_install_script), "a"): + with open(os.path.join(ovms_capi_dst_path, gpu_install_script), "a", encoding="utf-8"): pass dockerfile = f"Dockerfile.{UBUNTU if UBUNTU in base_os else base_os}" @@ -763,7 +763,7 @@ def log_configuration_variables(): pt_env_vars = list(filter(lambda x: x[0].startswith("TT_"), os.environ.items())) pt_env_vars.sort() for env_var in pt_env_vars: - logger.info("{}={}".format(*env_var)) + logger.info(f"{env_var[0]}={env_var[1]}") def mute_warnings(): @@ -1023,7 +1023,7 @@ def deselect(item, test_type, required_marker_ids, excluded_marker_ids): # make sure that item is not deselected by other marker return deselect_by_excluded_marker_ids(item, excluded_marker_ids) return True - elif excluded_marker_ids: + if excluded_marker_ids: return deselect_by_excluded_marker_ids(item, excluded_marker_ids) return False @@ -1113,7 +1113,7 @@ def log_labeled_stats(issues): msg = ["Skipped tests statistic:"] issues_sorted_by_quantity = sorted(issues.items(), key=lambda i: i[1], reverse=True) for issue, quantity in issues_sorted_by_quantity: - msg.append("{:>11}: {:>6}".format(issue, quantity)) + msg.append(f"{issue:>11}: {quantity:>6}") logger.info("\n".join(msg)) @@ -1121,8 +1121,8 @@ def log_others(other_items): msg = ["Skipped tests not labeled with issue:"] items_grouped_by_reason = groupby(other_items, key=lambda i: i.reason) for reason, items in list(items_grouped_by_reason): - msg.append("{}:".format(reason)) - msg.extend("|---{}".format(item.test_name) for item in list(items)) + msg.append(f"{reason}:") + msg.extend(f"|---{item.test_name}" for item in list(items)) logger.info("\n".join(msg)) diff --git a/tests/functional/utils/http/client_auth/auth.py b/tests/functional/utils/http/client_auth/auth.py index 63c2128d92..bde3bcde87 100644 --- a/tests/functional/utils/http/client_auth/auth.py +++ b/tests/functional/utils/http/client_auth/auth.py @@ -75,7 +75,7 @@ class ClientAuthType(Enum): OAUTH2_PROXY_AUTH = "OAuth2ProxyAuth" -class NoAuthConfigurationProvider(object): +class NoAuthConfigurationProvider: """Provide configuration for no client_auth http client.""" @classmethod @@ -88,7 +88,7 @@ def get(cls, url: str, proxies=None) -> HttpClientConfiguration: ) -class SslAuthConfigurationProvider(object): +class SslAuthConfigurationProvider: """Provide configuration for https client with SSL/TLS.""" @classmethod @@ -522,7 +522,7 @@ def _authorization_credentials_data(self): return data -class ClientAuthFactory(object): +class ClientAuthFactory: """Client authentication factory.""" EMPTY_URL = "" diff --git a/tests/functional/utils/http/client_auth/base.py b/tests/functional/utils/http/client_auth/base.py index 4b5c4570c9..3fe60eb430 100644 --- a/tests/functional/utils/http/client_auth/base.py +++ b/tests/functional/utils/http/client_auth/base.py @@ -22,7 +22,7 @@ from tests.functional.utils.http.http_session import HttpSession -class ClientAuthBase(object, metaclass=ABCMeta): +class ClientAuthBase(metaclass=ABCMeta): """Base class that all http client authentication implementations derive from. It performs automatic authentication. diff --git a/tests/functional/utils/http/http_client.py b/tests/functional/utils/http/http_client.py index a104120ae2..4088edacd8 100644 --- a/tests/functional/utils/http/http_client.py +++ b/tests/functional/utils/http/http_client.py @@ -19,7 +19,7 @@ from tests.functional.utils.http.http_session import HttpSession -class HttpClient(object): +class HttpClient: """Http api client.""" def __init__(self, url: str, auth: ClientAuthBase): diff --git a/tests/functional/utils/http/http_client_configuration.py b/tests/functional/utils/http/http_client_configuration.py index 70dcb7e2cf..bd440a6eea 100644 --- a/tests/functional/utils/http/http_client_configuration.py +++ b/tests/functional/utils/http/http_client_configuration.py @@ -25,7 +25,7 @@ # pylint: disable=too-many-instance-attributes -class HttpClientConfiguration(object): +class HttpClientConfiguration: """Http client configuration.""" identity_attributes = ("client_type", "url", "username", "password") diff --git a/tests/functional/utils/http/http_client_factory.py b/tests/functional/utils/http/http_client_factory.py index b894352239..8e750a7b4b 100644 --- a/tests/functional/utils/http/http_client_factory.py +++ b/tests/functional/utils/http/http_client_factory.py @@ -21,7 +21,7 @@ from tests.functional.utils.http.http_client_configuration import HttpClientConfiguration -class HttpClientFactory(object): +class HttpClientFactory: """Http client factory with implemented singleton behaviour for each generated client.""" _INSTANCES = {} diff --git a/tests/functional/utils/http/http_session.py b/tests/functional/utils/http/http_session.py index 77ec957061..38971f19eb 100644 --- a/tests/functional/utils/http/http_session.py +++ b/tests/functional/utils/http/http_session.py @@ -36,7 +36,7 @@ logger = get_logger(__name__) -class HttpSession(object, metaclass=ABCMeta): +class HttpSession(metaclass=ABCMeta): """HttpSession is wrapper for the Session class from the requests library. It stores the information about the username, password, possible proxies and certificates. diff --git a/tests/functional/utils/log_monitor.py b/tests/functional/utils/log_monitor.py index 9b7dc154fd..be542cacb0 100644 --- a/tests/functional/utils/log_monitor.py +++ b/tests/functional/utils/log_monitor.py @@ -165,7 +165,7 @@ def wait_for_messages( if found_lines: recent = found_lines[-5:] logger.debug( - f"[wait_for_messages] Last OVMS output:\n" + "[wait_for_messages] Last OVMS output:\n" + "\n".join(f" {line}" for line in recent) ) last_progress_log_time = now @@ -257,8 +257,7 @@ def find_messages(self, messages_to_find, raise_exception_if_not_found=False): log_line = self._read_log_line() if log_line is None: break - else: - found_lines.append(log_line) + found_lines.append(log_line) for specific_msg in messages_to_find_vs_results_map: if messages_to_find_vs_results_map[specific_msg] is None: diff --git a/tests/functional/utils/logger.py b/tests/functional/utils/logger.py index f2ee64a46e..4d630a7817 100644 --- a/tests/functional/utils/logger.py +++ b/tests/functional/utils/logger.py @@ -154,7 +154,7 @@ def strip_sensitive_str_values(self, data: str) -> str: return stripped_data -class LoggerType(object): +class LoggerType: """Logger types definitions""" HTTP_REQUEST = "http_request" HTTP_RESPONSE = "http_response" diff --git a/tests/functional/utils/marks.py b/tests/functional/utils/marks.py index f6ffbf11c5..cc274bce7c 100644 --- a/tests/functional/utils/marks.py +++ b/tests/functional/utils/marks.py @@ -84,10 +84,9 @@ def _params_phrase_match_test_params(cls, params, item): test_params = item.keywords.node.callspec.id if isinstance(params, Pattern): return bool(params.match(test_params)) - elif isinstance(params, str): + if isinstance(params, str): return params == test_params - else: - raise AttributeError(f"Unexpected conditional marker params {params}") + raise AttributeError(f"Unexpected conditional marker params {params}") return True @classmethod diff --git a/tests/functional/utils/numpy_loader.py b/tests/functional/utils/numpy_loader.py index 8a982f91ff..4e043096be 100644 --- a/tests/functional/utils/numpy_loader.py +++ b/tests/functional/utils/numpy_loader.py @@ -75,10 +75,9 @@ def load_labels(path): labels_extension = path.split(sep=".")[-1] if labels_extension == "npy": return load_npy_labels(path=path) - elif labels_extension in ["txt", "json"]: + if labels_extension in ["txt", "json"]: raise NotImplementedError() - else: - raise RuntimeError(f"Incorrect label data type: {labels_extension}") + raise RuntimeError(f"Incorrect label data type: {labels_extension}") def load_images(data_path, height, width, ids): @@ -88,7 +87,7 @@ def load_images(data_path, height, width, ids): assert file_extension in ['jpg', 'jpeg'] inputs = load_jpeg(data_path, height, width, ids) return inputs - elif os.path.isdir(data_path): + if os.path.isdir(data_path): inputs = [] images = list(filter(lambda x: re.match(r".+\.jpe?g", x.lower()), os.listdir(data_path))) for img in images: diff --git a/tests/functional/utils/process.py b/tests/functional/utils/process.py index 8e7a3c73c3..99c50f1caf 100644 --- a/tests/functional/utils/process.py +++ b/tests/functional/utils/process.py @@ -457,7 +457,7 @@ def wait_thread_end(self, timeout=None): def get_pid_details_as_dict(pid): try: - proc_status = Path(f"/proc/{pid}/status").read_text() + proc_status = Path(f"/proc/{pid}/status").read_text(encoding="utf-8") proc_status_dict = {} for line in proc_status.splitlines(): key, *val = line.split(":") # if value contains multiple ':' len(val) > 1 diff --git a/tests/functional/utils/reservation_manager/locker.py b/tests/functional/utils/reservation_manager/locker.py index 880b78293b..c07f1806bd 100644 --- a/tests/functional/utils/reservation_manager/locker.py +++ b/tests/functional/utils/reservation_manager/locker.py @@ -17,7 +17,7 @@ import filelock -class Locker(object): +class Locker: """Manage reservation lock across multiple reservation manager processes""" def __init__( self, diff --git a/tests/functional/utils/reservation_manager/manager.py b/tests/functional/utils/reservation_manager/manager.py index 0feb6177e3..1040ad8ccb 100644 --- a/tests/functional/utils/reservation_manager/manager.py +++ b/tests/functional/utils/reservation_manager/manager.py @@ -246,7 +246,7 @@ def reservation_from_json(self, json_path): """Return reservation from json data""" try: - with open(json_path, "r") as json_file: + with open(json_path, "r", encoding="utf-8") as json_file: json_data = json.load(json_file) logger.info(f"json_data: {json_data}") @@ -295,7 +295,7 @@ def create(self, verbose=False): # # Open exclusively, if file exists - throw exception try: - with open(json_save_path, "x") as json_file: + with open(json_save_path, "x", encoding="utf-8") as json_file: json.dump(reservation_json, json_file, ensure_ascii=False, @@ -305,7 +305,7 @@ def create(self, verbose=False): raise FileExistsError(f"Can't save reservation json: {exc}") from exc try: - with open(shell_env_save_path, "x") as shell_env_file: + with open(shell_env_save_path, "x", encoding="utf-8") as shell_env_file: shell_env_file.write(res_shell_envs) except FileExistsError as exc: @@ -387,7 +387,7 @@ def manager_from_args(args): config = None try: - with open(config_path, 'r') as file: + with open(config_path, 'r', encoding='utf-8') as file: config = yaml.load(file, Loader=yaml.FullLoader)["config"] except FileNotFoundError as exc: @@ -536,14 +536,14 @@ def is_intersect_with(self, pool_part): is in intersect with this instance. """ - if type(pool_part) is PoolPart: + if isinstance(pool_part, PoolPart): self_is_subset = (self.range.start in pool_part.range or self.range[-1] in pool_part.range) pool_part_is_subset = (pool_part.range.start in self.range or pool_part.range[-1] in self.range) return self_is_subset or pool_part_is_subset - elif type(pool_part) is Reservation: + if isinstance(pool_part, Reservation): res_pool_part = pool_part.pool_part.range self_is_subset = (self.range.start in res_pool_part From a6b7189833b3d1903921313635959e809d24ee21 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 26 Aug 2026 11:38:51 +0200 Subject: [PATCH 04/13] pytlint fixes part 3 --- tests/functional/constants/metrics.py | 11 ++++---- tests/functional/constants/paths.py | 2 +- tests/functional/constants/pipelines.py | 9 ++++--- .../data/ovms_capi_wrapper/ovms_autopxd.py | 2 +- .../data/ovms_capi_wrapper/setup.py | 1 + .../ovms_basic/python_model.py | 2 +- .../ovms_basic/python_model_loopback.py | 2 +- tests/functional/fixtures/ovms.py | 4 +-- .../object_model/dmesg_log_monitor.py | 4 +-- .../object_model/inference_helpers.py | 9 ++++--- .../object_model/mediapipe_calculators.py | 5 ++-- tests/functional/object_model/ovms_binary.py | 10 ++++--- tests/functional/object_model/ovms_capi.py | 3 ++- tests/functional/object_model/ovms_config.py | 14 +++++----- tests/functional/object_model/ovms_docker.py | 11 ++++---- tests/functional/object_model/ovms_info.py | 2 +- .../functional/object_model/ovms_instance.py | 13 +++++----- .../object_model/ovms_log_monitor.py | 17 ++++++------ tests/functional/object_model/ovsa.py | 2 +- .../object_model/package_manager.py | 4 +-- tests/functional/object_model/shape.py | 4 +-- tests/functional/object_model/test_helpers.py | 26 +++++++++---------- tests/functional/pylintrc | 16 ++++++++---- tests/functional/utils/assertions.py | 10 ++++--- tests/functional/utils/core.py | 9 ++++--- tests/functional/utils/docker.py | 6 +++-- tests/functional/utils/git_operations.py | 2 +- tests/functional/utils/hooks.py | 12 ++++----- .../functional/utils/http/client_auth/auth.py | 1 + tests/functional/utils/http/http_session.py | 1 + tests/functional/utils/inference/capi.py | 1 + .../utils/inference/communication/base.py | 1 + .../utils/inference/communication/grpc.py | 1 + .../utils/inference/communication/rest.py | 5 ++-- .../utils/inference/serving/base.py | 1 + .../functional/utils/inference/serving/kf.py | 11 ++++---- .../utils/inference/serving/openai.py | 3 ++- .../utils/inference/serving/triton.py | 2 +- tests/functional/utils/log_monitor.py | 13 +++++----- tests/functional/utils/marks.py | 5 ++-- tests/functional/utils/numpy_loader.py | 6 ++--- tests/functional/utils/port_manager.py | 3 ++- tests/functional/utils/process.py | 4 +-- .../unittests/test_manager.py | 2 +- 44 files changed, 153 insertions(+), 119 deletions(-) diff --git a/tests/functional/constants/metrics.py b/tests/functional/constants/metrics.py index 96a22f3301..5e6c73adf7 100644 --- a/tests/functional/constants/metrics.py +++ b/tests/functional/constants/metrics.py @@ -26,6 +26,7 @@ # ovms_infer_req_queue_size, ovms_infer_req_active # Config: # "monitoring": {"metrics": {"enable": true, "metrics_list": [...]}} +# pylint: disable=unused-argument import re from enum import Enum, auto @@ -88,11 +89,11 @@ class Metric: Wait_for_inference_histogram: Type_histogram, } - Default_names = [x for x in Default.keys()] + Default_names = list(Default.keys()) Additional = {"ovms_infer_req_queue_size": Type_gauge, "ovms_infer_req_active": Type_gauge} - Additional_names = [x for x in Additional.keys()] + Additional_names = list(Additional.keys()) Models_only = [ Current_request, @@ -296,7 +297,7 @@ def create_infer_histogram_metrics(model, ovms_run=None): def __init__(self, metric_name, content: dict, value=0): self.name = metric_name self.content = content - self.keys = [x for x in content] + self.keys = list(content) self.value = value def get_type(self): @@ -311,7 +312,7 @@ def get_type(self): Metric.Inference_histogram, Metric.Wait_for_inference_histogram, ] - if any([x in self.name for x in histogram_metrics]): + if any(x in self.name for x in histogram_metrics): result = Metric.Type_histogram return result @@ -372,7 +373,7 @@ def create_from_model_list(model_list, ovms_run=None, metrics=None): else: metric_list += Metrics._fill_method[metric](model=model, ovms_run=ovms_run) - """ + """ The following metrics are not multiplied for each model version (should occur once for single model name) ovms_requests_success[{'api': 'KServe', 'interface': 'gRPC', 'method': 'ModelReady', 'name': 'resnet-50-tf'}] 0 ovms_requests_success[{'api': 'KServe', 'interface': 'REST', 'method': 'ModelReady', 'name': 'resnet-50-tf'}] 0 diff --git a/tests/functional/constants/paths.py b/tests/functional/constants/paths.py index 9824023a9f..deebd6ac92 100644 --- a/tests/functional/constants/paths.py +++ b/tests/functional/constants/paths.py @@ -89,4 +89,4 @@ def get_target_device_lock_file(target_device, i): def any_is_relative_to(paths, subpath): - return any([_path in subpath for _path in paths]) + return any(_path in subpath for _path in paths) diff --git a/tests/functional/constants/pipelines.py b/tests/functional/constants/pipelines.py index 651839282b..09fa7dc9fb 100644 --- a/tests/functional/constants/pipelines.py +++ b/tests/functional/constants/pipelines.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import os from abc import abstractmethod @@ -413,7 +414,7 @@ def prepare_pipeline_input_data(self, batch_size=None, random_data=False): ) if demultiply_count is not None: dumultipy_content = [] - for i in range(number_of_batches_in_request): + for _i in range(number_of_batches_in_request): dumultipy_content.append(input_data[input_name]) input_data[input_name] = np.array(dumultipy_content) else: @@ -431,10 +432,10 @@ def prepare_input_data(self, batch_size=None, input_key=None): return data def prepare_model_input_data(self, batch_size=None): - return super(Pipeline, self).prepare_input_data(batch_size) + return super().prepare_input_data(batch_size) def prepare_model_resources(self, base_location): - return super(Pipeline, self).prepare_resources(base_location) + return super().prepare_resources(base_location) def map_inputs(self, prepare_inputs: dict): result_dict = {} @@ -1059,7 +1060,7 @@ def get_mediapipe_names(config): def prepare_input_data(self, batch_size=None, input_key=None): data = self.prepare_pipeline_input_data(batch_size) new_data = {} - for i, key in enumerate(list(data.keys()), start=0): + for _i, key in enumerate(list(data.keys()), start=0): new_input_key = input_key if input_key is not None else "input" new_data.update({new_input_key: data[key]}) return new_data diff --git a/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py b/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py index 5946098ca1..f136365062 100644 --- a/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py +++ b/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py @@ -64,7 +64,7 @@ def translate(self, code): parser = ArgumentParser(description="Script translates OVMS header file to .pxd file") parser.add_argument("-i", "--input_file", help="OVMS header file path") parser.add_argument("-o", "--output_file", help=".pxd output file path") - + args = parser.parse_args() if len(sys.argv) !=5: diff --git a/tests/functional/data/ovms_capi_wrapper/setup.py b/tests/functional/data/ovms_capi_wrapper/setup.py index 2ad71fee96..af350e535c 100644 --- a/tests/functional/data/ovms_capi_wrapper/setup.py +++ b/tests/functional/data/ovms_capi_wrapper/setup.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import os from distutils.core import setup diff --git a/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py b/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py index 0bf0259837..5795dd3e9f 100644 --- a/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py +++ b/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py @@ -28,7 +28,7 @@ def initialize(self, kwargs: dict): self.class_methods = { name: func for name, func in inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction) } - + def execute(self, inputs: list): text_input_data = [bytes(input).decode() for input in inputs] diff --git a/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py b/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py index db9e70096f..3f38296529 100644 --- a/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py +++ b/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py @@ -24,7 +24,7 @@ def initialize(self, kwargs: dict): self.output_names = [output_name for output_name in kwargs["output_names"] if output_name != "loopback"] self.class_methods = {name: func for name, func in inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)} - + def execute(self, inputs: list): input_data = inputs[0] text = bytes(input_data).decode() diff --git a/tests/functional/fixtures/ovms.py b/tests/functional/fixtures/ovms.py index 1dfb4f91e5..1af14b0362 100644 --- a/tests/functional/fixtures/ovms.py +++ b/tests/functional/fixtures/ovms.py @@ -92,8 +92,8 @@ def context(request, sigterm_cleaner, target_device, ovms_type, base_os): classes_with_external_libraries_used = ["TestByXCli2"] use_ovms_testing_image = any([ reqids_node - and any([x in requirements_with_external_libraries for x in reqids_node[0].args]), # By requirement id - reqids_parent and any([x in requirements_with_external_libraries for x in reqids_parent[0].args]), + and any(x in requirements_with_external_libraries for x in reqids_node[0].args), # By requirement id + reqids_parent and any(x in requirements_with_external_libraries for x in reqids_parent[0].args), request.node.parent.name in classes_with_external_libraries_used, ]) # Currently we enable testing image only for test that require custom build binaries: diff --git a/tests/functional/object_model/dmesg_log_monitor.py b/tests/functional/object_model/dmesg_log_monitor.py index 84c4c76ecf..ab23ec14ea 100644 --- a/tests/functional/object_model/dmesg_log_monitor.py +++ b/tests/functional/object_model/dmesg_log_monitor.py @@ -147,11 +147,11 @@ def raise_on_unexpected_messages(self, logs=None, filter_known_messages=False): logger.info(f"Dmesg OVMS process ID: {self.ovms_pid}") if unexpected_messages: dmesg_exceptions = get_children_from_module(DmesgError, assertions_module) # [(name, class_def), ...] - for name, exception_class in dmesg_exceptions: + for _name, exception_class in dmesg_exceptions: msg = getattr(exception_class, "msg", None) regex = getattr(exception_class, "regex", None) if (msg and any(filter(lambda x: msg in x, unexpected_messages))) or ( - regex and any(filter(lambda x: regex.match(x), unexpected_messages)) + regex and any(filter(regex.match, unexpected_messages)) ): logger.error(f"Found unexpected message in dmesg logs: {msg}") raise exception_class("\n".join(unexpected_messages), dmesg_log=logs) diff --git a/tests/functional/object_model/inference_helpers.py b/tests/functional/object_model/inference_helpers.py index ab1f5ff5ee..89ca7f7db5 100644 --- a/tests/functional/object_model/inference_helpers.py +++ b/tests/functional/object_model/inference_helpers.py @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import base64 import json import os import struct import time -import cohere from collections import defaultdict from copy import deepcopy from dataclasses import dataclass, field @@ -30,6 +30,7 @@ from threading import Event from typing import List, Union +import cohere import grpc import numpy as np import requests @@ -178,7 +179,7 @@ def get_expected_output_shape(self): @dataclass(frozen=False) class BinaryInferenceRequest(InferenceRequest): layout: str = Ovms.BINARY_IO_LAYOUT_ROW_NAME - dataset: ModelDataset = field(default_factory=lambda: DefaultBinaryDataset()) + dataset: ModelDataset = field(default_factory=DefaultBinaryDataset) format: str = None validate_match: bool = True batch_size: int = 1 @@ -823,7 +824,7 @@ def get_model_status(client, accepted_model_states=None, model_version=None, por def get_multiple_model_status(models_and_expected_state): for client, state in models_and_expected_state: try: - model_state = get_model_status(client, accepted_model_states=[state]) + _model_state = get_model_status(client, accepted_model_states=[state]) except (_InactiveRpcError, UnexpectedResponseError) as e: if state in [Ovms.ModelStatus.UNKNOWN, Ovms.ModelStatus.UNDEFINED]: pass # It is expected exceptions for given ModelStatus so proceed. @@ -883,7 +884,7 @@ def wait_for_model_meta(client, model, wait_time=1): received_meta_str = json.loads(response) logger.info(f"Expected metadata received for model {model.name}:\r\n{received_meta_str}") break - except (RpcError, AssertionError) as ex: + except (RpcError, AssertionError) as _ex: time.sleep(wait_time) assert validation_passed, f"Unexpected model metadata, current: {received_meta} for model: {model}" diff --git a/tests/functional/object_model/mediapipe_calculators.py b/tests/functional/object_model/mediapipe_calculators.py index c47befbb63..9d747183fd 100644 --- a/tests/functional/object_model/mediapipe_calculators.py +++ b/tests/functional/object_model/mediapipe_calculators.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import json import os @@ -128,7 +129,7 @@ def prepare_proto_calculator(cls, parameters, config_path_on_host, config_file=N contents.update({calc.name: calc.create_proto_content(model=model)}) content_to_save = " ".join(value for key, value in contents.items()) - if all(["pbtxt" in elem for elem in mediapipe_model_graph_paths]): + if all("pbtxt" in elem for elem in mediapipe_model_graph_paths): for path in mediapipe_model_graph_paths: filename = os.path.basename(path) cls.save(mediapipe_model, content_to_save, dst_path=dst_path, filename=filename) @@ -415,7 +416,7 @@ def create_proto_content( create_header=True, ): model = self.model if self.model is not None else model - ovms_ov_content = super(CorruptedFileCalculator, self).create_proto_content(model) + ovms_ov_content = super().create_proto_content(model) content = ovms_ov_content.replace("input_stream", self.name) return content diff --git a/tests/functional/object_model/ovms_binary.py b/tests/functional/object_model/ovms_binary.py index 2bad42794d..369700186e 100644 --- a/tests/functional/object_model/ovms_binary.py +++ b/tests/functional/object_model/ovms_binary.py @@ -13,14 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import json import os import subprocess -import psutil from datetime import datetime from pathlib import Path +import psutil + from tests.functional.utils.context import Context from tests.functional.utils.logger import get_logger from tests.functional.constants.os_type import OsType @@ -61,7 +63,7 @@ def start_binary_ovms( bool(parameters.use_config) or bool(parameters.custom_config) or (parameters.models is not None - and (any([model.is_pipeline() for model in parameters.models]) + and (any(model.is_pipeline() for model in parameters.models) or len(parameters.models) > 1 or any(model.is_mediapipe and not model.single_mediapipe_model_mode for model in parameters.models))) ) @@ -91,7 +93,7 @@ def start_binary_ovms( OvmsConfig.replace_subconfig_paths(parameters.name, subconfig_path, resources_dir) else: config_dir_path_on_host = os.path.join(TestEnvironment.current.base_dir, parameters.name, Paths.MODELS_PATH_NAME) - subconfig_dict, subconfig_path = OvmsConfig.create_subconfig(parameters.name, parameters, config_dir_path_on_host) + _subconfig_dict, subconfig_path = OvmsConfig.create_subconfig(parameters.name, parameters, config_dir_path_on_host) OvmsConfig.replace_subconfig_paths(parameters.name, subconfig_path, resources_dir) if parameters.models is not None and any(model.is_mediapipe for model in parameters.models): @@ -343,7 +345,7 @@ def update_model_list_and_config( params=None, **kwargs ): - resources_dir, models_dir_on_host = TestEnvironment.current.prepare_container_folders(name, models) + resources_dir, _models_dir_on_host = TestEnvironment.current.prepare_container_folders(name, models) if models_to_verify: ovms_log = self.create_log(False) diff --git a/tests/functional/object_model/ovms_capi.py b/tests/functional/object_model/ovms_capi.py index f3b368ca10..89b427034c 100644 --- a/tests/functional/object_model/ovms_capi.py +++ b/tests/functional/object_model/ovms_capi.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import os import pickle @@ -137,7 +138,7 @@ def execute_command(self, cmd, cwd=None): # Execute python api command without any validation (sic!). # Expect valid command sent by host process. print(cmd) - result = exec(cmd) + _result = exec(cmd) except Exception as e: logger.exception(e) diff --git a/tests/functional/object_model/ovms_config.py b/tests/functional/object_model/ovms_config.py index 277403195c..639620e6e5 100644 --- a/tests/functional/object_model/ovms_config.py +++ b/tests/functional/object_model/ovms_config.py @@ -162,7 +162,7 @@ def build_ovms_config( config = {Config.MODEL_CONFIG_LIST: []} else: config = {Config.MODEL_CONFIG_LIST: [model.get_config() for model in models]} - if all([c is None for c in config[Config.MODEL_CONFIG_LIST]]): + if all(c is None for c in config[Config.MODEL_CONFIG_LIST]): config = {Config.MODEL_CONFIG_LIST: []} if resource_dir and CurrentOvmsType.ovms_type in [ OvmsType.CAPI, @@ -203,7 +203,7 @@ def build_ovms_config( ): model["base_path"] = os.path.join(resource_dir, f'./{model["base_path"]}') - loader_configs = set([model.custom_loader.loader_config for model in models if model.custom_loader is not None]) + loader_configs = {model.custom_loader.loader_config for model in models if model.custom_loader is not None} for loader in loader_configs: if ( resource_dir @@ -315,17 +315,17 @@ def create_subconfig(name, parameters, config_path_on_host): subconfig_dict = {Config.MODEL_CONFIG_LIST: []} mediapipe_model = [model for model in parameters.models if model.is_mediapipe][0] - feature_extraction_models = \ + _feature_extraction_models = \ [ model for model in parameters.models if hasattr(model, "is_feature_extraction") and model.is_feature_extraction ] - rerank_models = \ + _rerank_models = \ [ model for model in parameters.models if hasattr(model, "is_rerank") and model.is_rerank ] - regular_models = [model for model in mediapipe_model.regular_models] + regular_models = list(mediapipe_model.regular_models) filename = Paths.SUBCONFIG_FILE_NAME subconfigs = [os.path.basename(elem.get("subconfig", "")) @@ -338,8 +338,8 @@ def create_subconfig(name, parameters, config_path_on_host): subconfig_dict[Config.MODEL_CONFIG_LIST].append(model.get_config()) filename = ( f"subconfig_{model.name}.json" - if config_dict is not None and all(["subconfig" in elem - for elem in config_dict[Config.MEDIAPIPE_CONFIG_LIST]]) + if config_dict is not None and all("subconfig" in elem + for elem in config_dict[Config.MEDIAPIPE_CONFIG_LIST]) else Paths.SUBCONFIG_FILE_NAME ) mediapipe_resources_path = os.path.join(config_path_on_host, mediapipe_model.name) diff --git a/tests/functional/object_model/ovms_docker.py b/tests/functional/object_model/ovms_docker.py index 1cd09d5444..851a3a6d16 100644 --- a/tests/functional/object_model/ovms_docker.py +++ b/tests/functional/object_model/ovms_docker.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import os import re @@ -216,11 +217,11 @@ def prepare_models_mapping(cls, context, ovms_container, models): if model.use_mapping is True: if mapping_exists: # Delete original mapping since it is tested in case: `default_model_mapping` - OvmsMappingConfig.delete_mapping(model) + OvmsMappingConfig.delete_mapping(ovms_container, model) OvmsMappingConfig.generate(model, context) # create generic mapping if model.use_mapping is False: if mapping_exists: - OvmsMappingConfig.delete_mapping(model) # just delete mapping + OvmsMappingConfig.delete_mapping(ovms_container, model) # just delete mapping @classmethod def build_ovms_instance_params(cls, context: Context, parameters: OvmsDockerParams): @@ -402,7 +403,7 @@ def build_ovms_instance_params(cls, context: Context, parameters: OvmsDockerPara @classmethod def create_config(cls, parameters, name): regular_models = parameters.get_regular_models() - using_custom_loader = any([(x.custom_loader is not None) for x in regular_models]) + using_custom_loader = any((x.custom_loader is not None) for x in regular_models) if using_custom_loader and not parameters.use_config: msg = "Custom loader is supported only with config file passed with --config_path." logger.error(msg) @@ -578,7 +579,7 @@ def execute_command(self, cmd, stream=False, cwd=None, workdir=None): def get_short_id(self): return self.container.container.short_id - def cleanup(self): + def cleanup(self, timeout=30): if not self.container.deleted: try: super().cleanup() @@ -694,7 +695,7 @@ def execute_command(self, cmd, stream=False, cwd=None): detach = "-d" else: detach = "" - exit_code, stdout, stderr = process.run(f"docker exec {detach} -u root {self.docker_id} {cmd}", cwd=cwd) + exit_code, stdout, _stderr = process.run(f"docker exec {detach} -u root {self.docker_id} {cmd}", cwd=cwd) return exit_code, stdout def get_env_variables(self): diff --git a/tests/functional/object_model/ovms_info.py b/tests/functional/object_model/ovms_info.py index 08fe0c01b7..13249497da 100644 --- a/tests/functional/object_model/ovms_info.py +++ b/tests/functional/object_model/ovms_info.py @@ -15,9 +15,9 @@ # import os +import re import docker -import re from tests.functional.utils.environment_info import DEFAULT_FULL_VERSION_NUMBER, BaseInfo from tests.functional.utils.logger import get_logger diff --git a/tests/functional/object_model/ovms_instance.py b/tests/functional/object_model/ovms_instance.py index 66991d3161..bc449e02a7 100644 --- a/tests/functional/object_model/ovms_instance.py +++ b/tests/functional/object_model/ovms_instance.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import os import random @@ -124,7 +125,7 @@ def get_break_msg_list(self, models): OvmsMessages.PIPELINE_REFERS_TO_INCORRECT_LIBRARY, ]) - if any([x.is_mediapipe for x in models]): + if any(x.is_mediapipe for x in models): break_msg_list.extend([ OvmsMessages.MEDIAPIPE_FAILED_TO_OPEN_GRAPH_SHORT, ]) @@ -147,7 +148,7 @@ def get_break_msg_list(self, models): [OvmsMessages.ERROR_FAILED_TO_CREATE_PLUGIN, OvmsMessages.ERROR_FAILED_TO_LOAD_LIBRARY] ) - if all([x.is_hf_direct_load and not x.is_local for x in models]): + if all(x.is_hf_direct_load and not x.is_local for x in models): break_msg_list.extend([ OvmsMessages.WARNING_NO_VERSION_FOUND_FOR_MODEL, ]) @@ -433,7 +434,7 @@ def cleanup(self, timeout=30): ) ovms_log = self._default_log.get_all_logs() dmesg_log = self._dmesg_log.get_all_logs() - for name, exception_class in dmesg_exceptions: + for _name, exception_class in dmesg_exceptions: msg = getattr(exception_class, "msg", None) for m in unexpected_messages: if m in msg: @@ -456,7 +457,7 @@ def cleanup(self, timeout=30): hasattr(self, "cmd") and self.cmd is not None and self.cmd.base_os == OsType.Windows and - type(error) == PermissionError + isinstance(error, PermissionError) ): change_dir_permissions(self.container_folder) shutil.rmtree(self.container_folder) @@ -472,7 +473,7 @@ def release_ports(self): @staticmethod def acquire_target_device_lock(target_device): - target_device = target_device.strip("'").split(" ")[0] if type(target_device) == str else target_device + target_device = target_device.strip("'").split(" ")[0] if isinstance(target_device, str) else target_device max_locks = MAX_WORKERS_PER_TARGET_DEVICE[get_base_device(target_device)] if max_locks == 0: # No lock required return None @@ -519,7 +520,7 @@ def stop_ovms_inside_kill(self, context, terminate_signal_type=Ovms.TERM_SIGNAL, logger.warning(e) proc = Process() short_id = self.get_short_id() - code, stdout, stderr = proc.run_and_check_return_all(f"docker ps -a --filter id={short_id}") + _code, stdout, _stderr = proc.run_and_check_return_all(f"docker ps -a --filter id={short_id}") assert short_id in stdout, f"OVMS is still running. Docker id: {short_id}, OVMS id: {ovms_pid}." diff --git a/tests/functional/object_model/ovms_log_monitor.py b/tests/functional/object_model/ovms_log_monitor.py index 6679110a6c..72b1fbf74c 100644 --- a/tests/functional/object_model/ovms_log_monitor.py +++ b/tests/functional/object_model/ovms_log_monitor.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import datetime import os @@ -73,7 +74,7 @@ def _calculate_batch_size_str(model): if model.input_shape_for_ovms is not None: input_shape = model.input_shape_for_ovms if isinstance(input_shape, dict): - input_shape = [x for x in input_shape.values()][0] + input_shape = list(input_shape.values())[0] if isinstance(input_shape, str): match = re.findall(r"([-\d:]+)", input_shape) @@ -234,7 +235,7 @@ def started(self, models, pipelines=None, timeout=None): if timeout is None: timeout = 60 if models: - timeout += sum([model.get_ovms_loading_time() for model in models]) + timeout += sum(model.get_ovms_loading_time() for model in models) self.ensure_contains_messages(msg_list, break_msg_list, timeout=timeout) def reloading(self, models, timeout=30): @@ -258,7 +259,7 @@ def models_unloaded(self, models, pipelines=None, timeout=None, ovms_instance=No if timeout is None: timeout = 60 if models: - timeout += sum([model.get_ovms_loading_time() for model in models]) + timeout += sum(model.get_ovms_loading_time() for model in models) self.ensure_contains_messages(msg_list, timeout=timeout, ovms_instance=ovms_instance) def models_loaded( @@ -267,7 +268,7 @@ def models_loaded( if timeout is None: timeout = wait_for_messages_timeout if models: - timeout += sum([model.get_ovms_loading_time() for model in models]) + timeout += sum(model.get_ovms_loading_time() for model in models) msg_list = self._get_log_models_loaded(models) if custom_msg_list is not None: msg_list.extend(custom_msg_list) @@ -307,7 +308,7 @@ def get_models_loading_time(self, models, is_reload): result = [] for model in models: if is_reload: - found_messages, messages_to_find_vs_results_map = self.find_messages( + _found_messages, messages_to_find_vs_results_map = self.find_messages( [OvmsMessages.MODEL_RELOADING.format(model.name)], raise_exception_if_not_found=True ) else: @@ -340,7 +341,7 @@ def find_no_of_infer_requests(log_lines): @staticmethod def get_status_change_from_logs(logs, expected_state=""): - status_change_messages = list(filter(lambda x: OvmsMessagesRegex.STATUS_CHANGE_RE.search(x), logs)) + status_change_messages = list(filter(OvmsMessagesRegex.STATUS_CHANGE_RE.search, logs)) status_change_messages = list(filter(lambda x: expected_state in x, status_change_messages)) return status_change_messages @@ -421,7 +422,7 @@ def get_all_logs(self): def get_logs_as_txt(self): process = Process() process.disable_check_stderr() - exit_code, stdout, _ = process.run(f"docker logs {self._container.id} 2>&1") + _exit_code, stdout, _ = process.run(f"docker logs {self._container.id} 2>&1") return stdout def is_ovms_running(self): @@ -451,6 +452,6 @@ def __init__(self, docker_id, **kwargs): self.process.set_log_silence() def get_all_logs(self): - exit_code, stdout, _ = self.process.run(f"docker logs {self.docker_id} 2>&1") + _exit_code, stdout, _ = self.process.run(f"docker logs {self.docker_id} 2>&1") self._read_lines = stdout.splitlines() return self._read_lines diff --git a/tests/functional/object_model/ovsa.py b/tests/functional/object_model/ovsa.py index 36c1880e28..4ea08ea2bf 100644 --- a/tests/functional/object_model/ovsa.py +++ b/tests/functional/object_model/ovsa.py @@ -122,7 +122,7 @@ def generate_ovsa_certs(mount_a_dir: bool = False, destination_path=OVSA.NGINX_T destination_path.mkdir(parents=True) logger.info("Generate Certs") - with SelfDeletingFileLock(f"{Path(destination_path, '.dir.lock')}") as fl: + with SelfDeletingFileLock(f"{Path(destination_path, '.dir.lock')}") as _fl: certs = OvsaCerts(mount_a_dir=mount_a_dir, nginx_mtls_path=destination_path) if certs.are_valid() and skip_if_valid: logger.info("Certificates are still valid and do not require generation") diff --git a/tests/functional/object_model/package_manager.py b/tests/functional/object_model/package_manager.py index 9bdfceece1..8ebf2e518e 100644 --- a/tests/functional/object_model/package_manager.py +++ b/tests/functional/object_model/package_manager.py @@ -116,7 +116,7 @@ def upgrade_packages(self, packages_to_upgrade, container_pkg_list): try: self.run_process(cmd, exception_type=InstallPkgVersionException) except InstallPkgVersionException as e: - cmd, retcode, stdout, stderr = e.get_process_details() + cmd, _retcode, stdout, stderr = e.get_process_details() if "The following packages have unmet dependencies" in stdout: logger.debug("Upgrading all system packages ...") self.run_process(self.upgrade_cmd, exception_type=UpgradePkgException) @@ -172,7 +172,7 @@ def get_list_of_installed_packages(self, container_id): for pkg in package_list: match = rpm_list_pkg_regexp.match(pkg) if match: - name, version, release, arch = match.groups() + name, version, _release, arch = match.groups() else: match = rpm_list_pkg_no_arch_regexp.match(pkg) assert match, f"Unable to parse package info: {pkg}" diff --git a/tests/functional/object_model/shape.py b/tests/functional/object_model/shape.py index 31b2c8397d..af53672b0a 100644 --- a/tests/functional/object_model/shape.py +++ b/tests/functional/object_model/shape.py @@ -108,8 +108,8 @@ def set_layout(self, _list, _layout=None): def init_by_list(self, _list, _layout=None): self.set_layout(_list, _layout) - for i in range(len(_list)): - setattr(self, self.layout[i], _list[i]) + for i, val in enumerate(_list): + setattr(self, self.layout[i], val) self[:] = _list[:] def get_shape_by_layout(self, layout=None): diff --git a/tests/functional/object_model/test_helpers.py b/tests/functional/object_model/test_helpers.py index 677a1411ee..4e263c5034 100644 --- a/tests/functional/object_model/test_helpers.py +++ b/tests/functional/object_model/test_helpers.py @@ -16,16 +16,16 @@ import concurrent.futures import enum -import requests - from collections import defaultdict from concurrent.futures import ThreadPoolExecutor from copy import deepcopy from http import HTTPStatus from math import prod -from retry.api import retry_call from statistics import mean +import requests +from retry.api import retry_call + from tests.functional.utils.assertions import InvalidReturnCodeException from tests.functional.utils.logger import get_logger, step @@ -50,7 +50,7 @@ def run_in_loop_during(action_to_run_in_loop, parallel_action, runs): def run_all_actions_in_loop(actions, runs, max_workers=None): - logger.info(f"Starting n={runs} parallel actions={','.join(map(lambda x: str(x), actions))}") + logger.info(f"Starting n={runs} parallel actions={','.join(map(str, actions))}") with ThreadPoolExecutor(max_workers) as executor: futures = [] for run in range(runs): @@ -156,10 +156,10 @@ def _generate_permutations(input_shape, shape_results, example_input_data_for_pr item = defaultdict(None) tmp_example_cnt = example_cnt.copy() tmp_example_cnt.reverse() - for in_name in input_array: + for in_name, in_values in input_array.items(): current_cnt = tmp_example_cnt.pop() idx = (j // prod(tmp_example_cnt)) % current_cnt - item[in_name] = input_array[in_name][idx] + item[in_name] = in_values[idx] example_array.append(item) result_predict_shape.append(example_array) return result[skip_first_items:], result_predict_shape[skip_first_items:] @@ -211,17 +211,17 @@ def generate_dynamic_shape_permutation(model): for i in range(2 ** len(shape)): new_shape = shape.copy() shape_for_predict_list = [shape.copy()] - for dim in range(len(shape)): + for dim, shape_val in enumerate(shape): if (i >> dim) % 2 == 1: new_shape[dim] = -1 copy_shape_for_predict_list = deepcopy(shape_for_predict_list) for for_predict, copy_for_predict in zip(shape_for_predict_list, copy_shape_for_predict_list): - for_predict[dim] = max(1, shape[dim] // 2) + for_predict[dim] = max(1, shape_val // 2) if layout is not None and layout.index("C") == dim: - copy_for_predict[dim] = shape[dim] + copy_for_predict[dim] = shape_val else: - copy_for_predict[dim] = shape[dim] * 2 + copy_for_predict[dim] = shape_val * 2 shape_for_predict_list += copy_shape_for_predict_list shape_results[in_name].append(f"({','.join([str(x) for x in new_shape])})") @@ -268,13 +268,13 @@ def generate_range_shape_permutation(model, skip_dims=2, generate_low_range=None for i in range(2 ** len(shape[skip_dims:])): new_shape = shape[skip_dims:] shape_for_predict_list = [shape.copy()] - for dim in range(len(new_shape)): + for dim, new_shape_val in enumerate(new_shape): if (i >> dim) % 2 == 1: - high_value = generate_high_range(new_shape[dim]) + high_value = generate_high_range(new_shape_val) if layout is not None and layout.index("C") == (dim + skip_dims): copy_for_predict[dim] = shape[dim] - new_shape[dim] = f"{generate_low_range(new_shape[dim])}:{high_value}" + new_shape[dim] = f"{generate_low_range(new_shape_val)}:{high_value}" copy_shape_for_predict_list = deepcopy(shape_for_predict_list) for for_predict, copy_for_predict in zip(shape_for_predict_list, copy_shape_for_predict_list): diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc index 68ac8f499b..5815517b0d 100644 --- a/tests/functional/pylintrc +++ b/tests/functional/pylintrc @@ -529,17 +529,23 @@ disable= useless-suppression, # (I0021) deprecated-pragma, # (I0022) use-symbolic-message-instead, # (I0023) + duplicate-code, # (R0801, to be FIXED) logging-fstring-interpolation, # (W1203, to be FIXED) logging-format-interpolation, # (W1202, to be FIXED) missing-function-docstring, # (C0116, to be FIXED) missing-class-docstring, # (C0115, to be FIXED) missing-module-docstring, # (C0114, to be FIXED) + protected-access, # (W0212, to be FIXED) too-few-public-methods, # (R0903, to be FIXED) - too-many-instance-attributes # (allowed intentionally) - too-many-lines, # (allowed intentionally) - too-many-statements, # (allowed intentionally) - too-many-locals, # (allowed intentionally) - too-many-branches, # (allowed intentionally) + too-many-arguments, # (allowed intentionally) + too-many-boolean-expressions, # (allowed intentionally) + too-many-instance-attributes, # (allowed intentionally) + too-many-lines, # (allowed intentionally) + too-many-nested-blocks, # (allowed intentionally) + too-many-positional-arguments, # (allowed intentionally) + too-many-statements, # (allowed intentionally) + too-many-locals, # (allowed intentionally) + too-many-branches, # (allowed intentionally) # Enable the message, report, category or checker with the given id(s). You can diff --git a/tests/functional/utils/assertions.py b/tests/functional/utils/assertions.py index a6aa23f721..317cba5d95 100644 --- a/tests/functional/utils/assertions.py +++ b/tests/functional/utils/assertions.py @@ -13,16 +13,18 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument -import grpc import json import os -import pytest import re -import yaml from pathlib import Path from typing import Callable, Type +import grpc +import pytest +import yaml + from tests.functional.utils.logger import get_logger from tests.functional.constants.ovms import CurrentOvmsType from tests.functional.constants.paths import Paths @@ -71,7 +73,7 @@ def __str__(self): class UnexpectedResponseError(OvmsTestException): def __init__(self, status=None, error_message=None, message=None): message = message or f"Code:{status} Message:{error_message}" - super(UnexpectedResponseError, self).__init__(message) + super().__init__(message) self.status = status self.error_message = error_message diff --git a/tests/functional/utils/core.py b/tests/functional/utils/core.py index 46ec8edfe9..f76c4b3a7d 100644 --- a/tests/functional/utils/core.py +++ b/tests/functional/utils/core.py @@ -22,11 +22,12 @@ from collections import defaultdict from datetime import datetime, timedelta from enum import Enum -from filelock import UnixFileLock, WindowsFileLock from pathlib import Path from tempfile import TemporaryDirectory from typing import Any +from filelock import UnixFileLock, WindowsFileLock + from tests.functional.constants.os_type import get_host_os, OsType @@ -54,7 +55,7 @@ def get_token_value(token_file_path, fallback_value=None): def get_username(): try: user_name = os.getlogin() - except OSError as e: + except OSError as _e: user = os.environ.get("USER", "not_known_user") logname = os.environ.get("LOGNAME", user) user_name = os.environ.get("USERNAME", logname) @@ -82,7 +83,7 @@ def acquire(self, **kwargs): def acquire_no_raise(self, timeout): try: self.acquire(timeout=timeout) - except TimeoutError as e: + except TimeoutError as _e: return False return True @@ -106,7 +107,7 @@ def acquire(self, **kwargs): def acquire_no_raise(self, timeout): try: self.acquire(timeout=timeout) - except TimeoutError as e: + except TimeoutError as _e: return False return True diff --git a/tests/functional/utils/docker.py b/tests/functional/utils/docker.py index b375c302f4..e87ed45d7e 100644 --- a/tests/functional/utils/docker.py +++ b/tests/functional/utils/docker.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import pprint import signal @@ -351,9 +352,10 @@ def get_logs(self, **kwargs) -> Union[bool, str]: @classmethod def check_not_on_list(cls, container: Union[str, "DockerContainer"], comparator: Callable[[Any, Any], bool] = None): current_list = cls.list() + items = '\n'.join([repr(elem) for elem in current_list]) logger.debug( f"Searching for container with a name: {container if isinstance(container, str) else container.name}, " - f"among:\n{'\n'.join([repr(elem) for elem in current_list])}\n" + f"among:\n{items}\n" ) if comparator is None: assert container not in current_list, f"{container} was found on: {pprint.pformat(current_list)}" @@ -376,7 +378,7 @@ def ensure_not_on_list( tries=cls.NOT_ON_LIST_RETRY["tries"], delay=cls.NOT_ON_LIST_RETRY["delay"], ) - for count in range(1, ensure_count): + for _count in range(1, ensure_count): time.sleep(cls.NOT_ON_LIST_RETRY["delay"]) cls.check_not_on_list(container, comparator) diff --git a/tests/functional/utils/git_operations.py b/tests/functional/utils/git_operations.py index 9bab7410ae..3f75fdfd67 100644 --- a/tests/functional/utils/git_operations.py +++ b/tests/functional/utils/git_operations.py @@ -32,7 +32,7 @@ def _get_current_git_repo_object(): return None try: repo = Repo(current_directory, search_parent_directories=True) - except (NoSuchPathError, InvalidGitRepositoryError) as e: + except (NoSuchPathError, InvalidGitRepositoryError) as _e: print(f"Cannot get repo from current directory: {current_directory}") return None return repo diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index 85fceece97..211c9f2345 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -21,12 +21,12 @@ import sys import time import warnings -import pytest - from collections import Counter, defaultdict, namedtuple -from docker import errors as docker_errors from itertools import groupby from pathlib import Path + +import pytest +from docker import errors as docker_errors from _pytest.mark import Mark, MarkDecorator from _pytest.python import Function @@ -332,7 +332,7 @@ def run_docker_build_ovms_image(cmd, ovms_image_name, cwd, timeout=None): print(f"Building {ovms_image_name} image using cmd: {cmd}") proc = Process() proc.disable_check_stderr() - code, stdout, stderr = proc.run_and_check_return_all(cmd, cwd=cwd, timeout=timeout) + _code, stdout, stderr = proc.run_and_check_return_all(cmd, cwd=cwd, timeout=timeout) assert (f"naming to {ovms_image_name}" in stderr) or ( f"Successfully tagged {ovms_image_name}" in stdout ), f"Image was not built successfully; stderr: {stderr}" @@ -425,8 +425,8 @@ def build_ovms_capi_image(): def prepare_ovms_package(): if all([ - all([OvmsType.CAPI not in ovms_type for ovms_type in config.ovms_types]), - all([OvmsType.BINARY not in ovms_type for ovms_type in config.ovms_types]), + all(OvmsType.CAPI not in ovms_type for ovms_type in config.ovms_types), + all(OvmsType.BINARY not in ovms_type for ovms_type in config.ovms_types), ]): return diff --git a/tests/functional/utils/http/client_auth/auth.py b/tests/functional/utils/http/client_auth/auth.py index bde3bcde87..a889dd3b5c 100644 --- a/tests/functional/utils/http/client_auth/auth.py +++ b/tests/functional/utils/http/client_auth/auth.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import pprint import time diff --git a/tests/functional/utils/http/http_session.py b/tests/functional/utils/http/http_session.py index 38971f19eb..3500c98dc3 100644 --- a/tests/functional/utils/http/http_session.py +++ b/tests/functional/utils/http/http_session.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument """Wrapper for the Session class from the requests library.""" diff --git a/tests/functional/utils/inference/capi.py b/tests/functional/utils/inference/capi.py index f7dc4b767f..997568e669 100644 --- a/tests/functional/utils/inference/capi.py +++ b/tests/functional/utils/inference/capi.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import pytest diff --git a/tests/functional/utils/inference/communication/base.py b/tests/functional/utils/inference/communication/base.py index 3d377ccd43..73c3d13db8 100644 --- a/tests/functional/utils/inference/communication/base.py +++ b/tests/functional/utils/inference/communication/base.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import abc diff --git a/tests/functional/utils/inference/communication/grpc.py b/tests/functional/utils/inference/communication/grpc.py index c212ffad81..4a1fd8e9b9 100644 --- a/tests/functional/utils/inference/communication/grpc.py +++ b/tests/functional/utils/inference/communication/grpc.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import json diff --git a/tests/functional/utils/inference/communication/rest.py b/tests/functional/utils/inference/communication/rest.py index 541920fb9c..f66f07f1d6 100644 --- a/tests/functional/utils/inference/communication/rest.py +++ b/tests/functional/utils/inference/communication/rest.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import json from http import HTTPStatus @@ -97,7 +98,7 @@ def send_predict_request(self, request, timeout, version=None): version = self.model.version if not version else version rest_path = self.get_rest_path(self.PREDICT, model_version=version) - data = request if type(request) == str else request.get('request', None) + data = request if isinstance(request, str) else request.get('request', None) try: headers = request.get('inference_header', None) except AttributeError: @@ -139,7 +140,7 @@ def set_serving_inputs_outputs(self, response): Parameters: response (GetModelMetadataResponse): inference response """ - signature_def = response.metadata['signature_def'] + _signature_def = response.metadata['signature_def'] # signature_map = get_model_metadata_pb2.SignatureDefMap() # signature_map.ParseFromString(signature_def.value) # serving_default = signature_map.ListFields()[0][1]['serving_default'] diff --git a/tests/functional/utils/inference/serving/base.py b/tests/functional/utils/inference/serving/base.py index 8815e7130d..0bd038fd1f 100644 --- a/tests/functional/utils/inference/serving/base.py +++ b/tests/functional/utils/inference/serving/base.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import abc diff --git a/tests/functional/utils/inference/serving/kf.py b/tests/functional/utils/inference/serving/kf.py index 81506b4016..8710466bc0 100644 --- a/tests/functional/utils/inference/serving/kf.py +++ b/tests/functional/utils/inference/serving/kf.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import enum import json @@ -259,7 +260,7 @@ def process_json_output(self, result_dict): def set_serving_inputs_outputs_grpc(self, response, model_name=None): model_name = model_name if model_name is not None else self.model_name assert response.name == model_name, f"Cannot find model_name={model_name} in response={response}" - versions = [int(v) for v in response.versions] + _versions = [int(v) for v in response.versions] self.model.inputs = {} self.model.outputs = {} @@ -365,18 +366,18 @@ def is_model_ready_rest(self, model_name, model_version=""): def _merge(self, input_list, output=None): if output is None: - if any(type(child) == bytes for child in input_list): + if any(isinstance(child, bytes) for child in input_list): assert all( - type(child) == bytes for child in input_list + isinstance(child, bytes) for child in input_list ), "Do not mix types, all inputs should be use bytes" output = b'' else: output = [] for children in input_list: - if type(children) == list: + if isinstance(children, list): self._merge(children, output) - elif type(children) == bytes: + elif isinstance(children, bytes): output += children else: output.append(children) diff --git a/tests/functional/utils/inference/serving/openai.py b/tests/functional/utils/inference/serving/openai.py index 6558e6f59d..078eef8fb3 100644 --- a/tests/functional/utils/inference/serving/openai.py +++ b/tests/functional/utils/inference/serving/openai.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument from dataclasses import dataclass from typing import Tuple, Union @@ -268,7 +269,7 @@ class OpenAIRequestParams: def prepare_dict(self, set_null_values=False, **kwargs): if set_null_values: - request_params_dict = {key: value for key, value in vars(self).items()} + request_params_dict = dict(vars(self).items()) else: request_params_dict = {key: value for key, value in vars(self).items() if value is not None} return request_params_dict diff --git a/tests/functional/utils/inference/serving/triton.py b/tests/functional/utils/inference/serving/triton.py index 35783f42fd..c6f1d5d4b7 100644 --- a/tests/functional/utils/inference/serving/triton.py +++ b/tests/functional/utils/inference/serving/triton.py @@ -172,7 +172,7 @@ def prepare_triton_input_data(self, input_data=None): def prepare_triton_output_data(self): outputs = [] - for i, out_model_name in enumerate(self.model.outputs): + for _i, out_model_name in enumerate(self.model.outputs): outputs.append(self.api_client.InferRequestedOutput(out_model_name)) return outputs diff --git a/tests/functional/utils/log_monitor.py b/tests/functional/utils/log_monitor.py index be542cacb0..4914a64d69 100644 --- a/tests/functional/utils/log_monitor.py +++ b/tests/functional/utils/log_monitor.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument import os from abc import ABC, abstractmethod @@ -174,8 +175,8 @@ def wait_for_messages( found_lines.append(log_line) - for specific_msg in messages_to_find_vs_results_map: - if messages_to_find_vs_results_map[specific_msg] is None: + for specific_msg, result in messages_to_find_vs_results_map.items(): + if result is None: if isinstance(specific_msg, str): messages_to_find_vs_results_map[specific_msg] = log_line if specific_msg in log_line \ else None @@ -259,11 +260,11 @@ def find_messages(self, messages_to_find, raise_exception_if_not_found=False): break found_lines.append(log_line) - for specific_msg in messages_to_find_vs_results_map: - if messages_to_find_vs_results_map[specific_msg] is None: + for specific_msg, result in messages_to_find_vs_results_map.items(): + if result is None: messages_to_find_vs_results_map[specific_msg] = log_line if specific_msg in log_line else None - all_messages_found = all([x for x in messages_to_find_vs_results_map.values()]) + all_messages_found = all(messages_to_find_vs_results_map.values()) self._log_search_info( raise_exception_if_not_found, all_messages_found, found_lines, messages_to_find_vs_results_map ) @@ -308,6 +309,6 @@ def _truncate_lines_for_exception(found_lines): head = found_lines[:head_count] tail = found_lines[-tail_count:] return "\n".join(head + [f"\n... [{omitted} lines omitted] ...\n"] + tail) - + def is_ovms_running(self): return True diff --git a/tests/functional/utils/marks.py b/tests/functional/utils/marks.py index cc274bce7c..5c1ed41023 100644 --- a/tests/functional/utils/marks.py +++ b/tests/functional/utils/marks.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unused-argument from enum import Enum from itertools import chain @@ -35,7 +36,7 @@ def __new__(cls, mark: str, description: str = None, *args): return obj def __init__(self, *args): - super(MarkMeta, self).__init__() + super().__init__() def __hash__(self) -> int: return hash(self.mark) @@ -314,7 +315,7 @@ class MarksRegistry(tuple): def __new__(cls) -> 'MarksRegistry': # noinspection PyTypeChecker - return tuple.__new__(cls, [mark for mark in chain(*cls.MARK_ENUMS)]) + return tuple.__new__(cls, list(chain(*cls.MARK_ENUMS))) @staticmethod def register(pytest_config): diff --git a/tests/functional/utils/numpy_loader.py b/tests/functional/utils/numpy_loader.py index 4e043096be..124bdb3d2f 100644 --- a/tests/functional/utils/numpy_loader.py +++ b/tests/functional/utils/numpy_loader.py @@ -48,7 +48,7 @@ def transpose_input(images, axes): def crop_resize(img, cropx, cropy): - y, x, c = img.shape + y, x, _c = img.shape if y < cropy: img = cv2.resize(img, (x, cropy)) y = cropy @@ -106,7 +106,7 @@ def load_images(data_path, height, width, ids): def load_numpy(data_path): assert os.path.isfile(data_path) - file_extension = os.path.basename(data_path).split(sep=".")[-1] + _file_extension = os.path.basename(data_path).split(sep=".")[-1] # optional preprocessing depending on the model data = np.load(data_path, mmap_mode='r+', allow_pickle=False) data = data - np.min(data) # Normalization 0-255 @@ -119,7 +119,7 @@ def load_numpy(data_path): def prepare_data(data_path, expected_shape, batch_size, transpose_axes=None, expected_layout=None, data_layout=None): - filename, file_extension = os.path.splitext(data_path) + _filename, file_extension = os.path.splitext(data_path) if file_extension == '.npy': data = load_numpy(data_path) else: diff --git a/tests/functional/utils/port_manager.py b/tests/functional/utils/port_manager.py index c3c20ddd02..8f42725d5b 100644 --- a/tests/functional/utils/port_manager.py +++ b/tests/functional/utils/port_manager.py @@ -15,10 +15,11 @@ # import errno -import psutil import socket import threading +import psutil + from tests.functional.utils.core import NamedSingletonMeta from tests.functional.utils.logger import get_logger from tests.functional.utils.helpers import get_xdist_worker_count, get_xdist_worker_nr diff --git a/tests/functional/utils/process.py b/tests/functional/utils/process.py index 99c50f1caf..3dab1f1ee7 100644 --- a/tests/functional/utils/process.py +++ b/tests/functional/utils/process.py @@ -249,7 +249,7 @@ def kill(self, force=False, timeout=30): try: parent_proc = psutil.Process(self._proc.pid) child_processes = parent_proc.children() - except psutil.NoSuchProcess as e: + except psutil.NoSuchProcess as _e: return not self.is_alive() for child_proc in child_processes: try: @@ -334,7 +334,7 @@ def _kill_by_shell(self, pid, end_time, force=False, sudo=False): class RemoteProcess(SSHClient, UnixProcess): def __init__(self, hostname, username=None, password=None, port=22): super(SSHClient, self).__init__() - super(RemoteProcess, self).__init__() + super().__init__() self._proc_stdout = None self._info = {'hostname': hostname, 'username': username, diff --git a/tests/functional/utils/reservation_manager/unittests/test_manager.py b/tests/functional/utils/reservation_manager/unittests/test_manager.py index 49d2af731c..d09ac86ef0 100644 --- a/tests/functional/utils/reservation_manager/unittests/test_manager.py +++ b/tests/functional/utils/reservation_manager/unittests/test_manager.py @@ -101,7 +101,7 @@ def test_ranges_good(self): for start, stop in TestManager.pool_part_ranges: try: PoolPart(start, stop) - except AssertionError as e: + except AssertionError as _e: pytest.fail(f"Creating PoolPart should succeed with range: " f"start {start}, stp: {stop}") From a73482eb3c86e60a407c5fc51da5103666425ec5 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 26 Aug 2026 12:38:54 +0200 Subject: [PATCH 05/13] pytlint fixes part 4 --- .../data/ovms_capi_wrapper/ovms_autopxd.py | 4 +-- .../data/ovms_capi_wrapper/setup.py | 9 +++--- .../ovms_basic/python_model.py | 4 +-- .../ovms_basic/python_model_loopback.py | 3 +- ..._loopback_multiple_use_of_valid_outputs.py | 4 +-- ..._model_loopback_return_instead_of_yield.py | 4 +-- ...l_writing_to_loopback_output_in_execute.py | 4 +-- tests/functional/fixtures/server.py | 1 - .../functional/object_model/ovms_instance.py | 1 - tests/functional/pylintrc | 29 ++++++++++--------- tests/functional/utils/assertions.py | 3 -- .../functional/utils/http/client_auth/auth.py | 26 +++++------------ .../functional/utils/http/client_auth/base.py | 1 - .../utils/http/http_client_factory.py | 28 ++++++------------ .../utils/http/http_socket_wrapper.py | 4 ++- 15 files changed, 48 insertions(+), 77 deletions(-) diff --git a/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py b/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py index f136365062..eeea251969 100644 --- a/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py +++ b/tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py @@ -43,8 +43,8 @@ def visit_Struct(self, node): if type_decl: # inline struct, add a reference to whatever name it was defined on the top level self.append(escape(name)) - else: - return self.visit_Block(node, kind) + return None + return self.visit_Block(node, kind) def translate(self, code): self.visit(parse(code=code)) diff --git a/tests/functional/data/ovms_capi_wrapper/setup.py b/tests/functional/data/ovms_capi_wrapper/setup.py index af350e535c..e0df647cd0 100644 --- a/tests/functional/data/ovms_capi_wrapper/setup.py +++ b/tests/functional/data/ovms_capi_wrapper/setup.py @@ -24,8 +24,7 @@ from pyximport import pyximport -def build_ovms_capi_wrapper(ovms_capi_wrapper_path, - capi_package_content_path): +def build_ovms_capi_wrapper(ovms_capi_wrapper_path, capi_package_content_path): includes_dir = str(Path(capi_package_content_path, "../include/")) lib_dir = str(Path(capi_package_content_path, "lib")) @@ -67,7 +66,9 @@ def prepare_dynamic_load(ovms_capi_wrapper_path, capi_package_content_path): if __name__ == "__main__": # Expect valid extracted capi package in `capi_package_content_path` - capi_cython_extensions = build_ovms_capi_wrapper(ovms_capi_wrapper_path=f"{os.getcwd()}/include/ovms_capi_wrapper.pyx", - capi_package_content_path=os.getcwd()) + capi_cython_extensions = build_ovms_capi_wrapper( + ovms_capi_wrapper_path=f"{os.getcwd()}/include/ovms_capi_wrapper.pyx", + capi_package_content_path=os.getcwd(), + ) extension = cythonize(capi_cython_extensions) setup(ext_modules=extension) diff --git a/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py b/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py index 5795dd3e9f..50f193fca8 100644 --- a/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py +++ b/tests/functional/data/python_custom_nodes/ovms_basic/python_model.py @@ -25,9 +25,7 @@ def initialize(self, kwargs: dict): self.node_name = kwargs["node_name"] self.input_names = kwargs["input_names"] self.output_names = kwargs["output_names"] - self.class_methods = { - name: func for name, func in inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction) - } + self.class_methods = dict(inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)) def execute(self, inputs: list): text_input_data = [bytes(input).decode() for input in inputs] diff --git a/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py b/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py index 3f38296529..c521d978a4 100644 --- a/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py +++ b/tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py @@ -22,8 +22,7 @@ def initialize(self, kwargs: dict): self.node_name = kwargs["node_name"] self.input_names = kwargs["input_names"] self.output_names = [output_name for output_name in kwargs["output_names"] if output_name != "loopback"] - self.class_methods = {name: func for name, func in - inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)} + self.class_methods = dict(inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)) def execute(self, inputs: list): input_data = inputs[0] diff --git a/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py b/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py index 653e83b0c3..eea3e6b789 100644 --- a/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py +++ b/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py @@ -22,9 +22,7 @@ def initialize(self, kwargs: dict): self.node_name = kwargs["node_name"] self.input_names = kwargs["input_names"] self.output_names = [output_name for output_name in kwargs["output_names"] if output_name != "loopback"] - self.class_methods = { - name: func for name, func in inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction) - } + self.class_methods = dict(inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)) self.alternative_input_text = "Alternative input text here" def execute(self, inputs: list): diff --git a/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py b/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py index de6be9c70c..1b8f9ba4ad 100644 --- a/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py +++ b/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py @@ -22,9 +22,7 @@ def initialize(self, kwargs: dict): self.node_name = kwargs["node_name"] self.input_names = kwargs["input_names"] self.output_names = [output_name for output_name in kwargs["output_names"] if output_name != "loopback"] - self.class_methods = { - name: func for name, func in inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction) - } + self.class_methods = dict(inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)) def execute(self, inputs: list): input_data = inputs[0] diff --git a/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py b/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py index 8cd3db94be..2b276e55e4 100644 --- a/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py +++ b/tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py @@ -22,9 +22,7 @@ def initialize(self, kwargs: dict): self.node_name = kwargs["node_name"] self.input_names = kwargs["input_names"] self.output_names = ["loopback"] - self.class_methods = { - name: func for name, func in inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction) - } + self.class_methods = dict(inspect.getmembers(OvmsPythonModel, predicate=inspect.isfunction)) print(f'kwargs in writing to loopback: {kwargs["output_names"]}') def execute(self, inputs: list): diff --git a/tests/functional/fixtures/server.py b/tests/functional/fixtures/server.py index d968c609bf..acbd93e807 100644 --- a/tests/functional/fixtures/server.py +++ b/tests/functional/fixtures/server.py @@ -59,7 +59,6 @@ def sigterm_handle(self, _signo, _stack_frame): item.cleanup() except (UnexpectedResponseError, AssertionError) as exc: logger.exception(str(exc)) - pass sys.exit(1) diff --git a/tests/functional/object_model/ovms_instance.py b/tests/functional/object_model/ovms_instance.py index bc449e02a7..b6f31ce45c 100644 --- a/tests/functional/object_model/ovms_instance.py +++ b/tests/functional/object_model/ovms_instance.py @@ -376,7 +376,6 @@ def fetch_and_store_ovms_pid(self, timeout=10): """ Fetch and save OVMS process id. """ - pass @abstractmethod def start(self, ensure_started=False, *args, **kwargs): diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc index 5815517b0d..ca9f1004ec 100644 --- a/tests/functional/pylintrc +++ b/tests/functional/pylintrc @@ -78,16 +78,16 @@ ignore-paths= constants/paths.py, constants/pipelines.py, # (R0801: duplicate code with models.py) constants/target_device_configuration.py, # W0105: String statement has no effect (pointless-string-statement) - data/ovms_capi_wrapper/ovms_autopxd.py, - data/ovms_capi_wrapper/setup.py, - data/python_custom_nodes/incrementer/incrementer.py, - data/python_custom_nodes/ovms_basic/python_model.py, - data/python_custom_nodes/ovms_basic/python_model_loopback.py, - data/python_custom_nodes/ovms_corrupted/python_model_corrupted_import.py, - data/python_custom_nodes/ovms_corrupted/python_model_exceptions.py, - data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py, - data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py, - data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py, + tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py, + tests/functional/data/ovms_capi_wrapper/setup.py, + tests/functional/data/python_custom_nodes/incrementer/incrementer.py, + tests/functional/data/python_custom_nodes/ovms_basic/python_model.py, + tests/functional/data/python_custom_nodes/ovms_basic/python_model_loopback.py, + tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_corrupted_import.py, + tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_exceptions.py, + tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py, + tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py, + tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py, fixtures/*, object_model/cpu_extension.py, # C0103: Attribute name doesn't conform to naming style (invalid-name) object_model/custom_loader.py, @@ -127,9 +127,9 @@ ignore-paths= utils/http/http_session.py, utils/http/http_socket_wrapper.py, utils/inference/capi.py, - utils/inference/communication/base.py, - utils/inference/communication/grpc.py, - utils/inference/communication/rest.py, + tests/functional/utils/inference/communication/base.py, + tests/functional/utils/inference/communication/grpc.py, + tests/functional/utils/inference/communication/rest.py, utils/inference/inference_client_factory.py, utils/inference/serving/base.py, utils/inference/serving/kf.py, @@ -529,6 +529,7 @@ disable= useless-suppression, # (I0021) deprecated-pragma, # (I0022) use-symbolic-message-instead, # (I0023) + inconsistent-return-statements, # (R1710, to be FIXED) duplicate-code, # (R0801, to be FIXED) logging-fstring-interpolation, # (W1203, to be FIXED) logging-format-interpolation, # (W1202, to be FIXED) @@ -543,9 +544,11 @@ disable= too-many-lines, # (allowed intentionally) too-many-nested-blocks, # (allowed intentionally) too-many-positional-arguments, # (allowed intentionally) + too-many-public-methods, # (allowed intentionally) too-many-statements, # (allowed intentionally) too-many-locals, # (allowed intentionally) too-many-branches, # (allowed intentionally) + too-many-return-statements, # (allowed intentionally) # Enable the message, report, category or checker with the given id(s). You can diff --git a/tests/functional/utils/assertions.py b/tests/functional/utils/assertions.py index 317cba5d95..8fd510ccb7 100644 --- a/tests/functional/utils/assertions.py +++ b/tests/functional/utils/assertions.py @@ -121,7 +121,6 @@ def _assert_status_code_and_message(status, error_message_phrase, status_code, e error_msg = yaml.load(error_msg, Loader=yaml.Loader) # convert dict saved as string except (yaml.scanner.ScannerError, yaml.parser.ParserError) as exception: e = exception - pass error_msg = error_msg["error"] if getattr(error_msg, "error", None) is not None else str(error_msg) assert error_message_phrase in error_msg, \ f"Expected output:\n{error_message_phrase}\nnot found in exception {e.__class__.__name__} value:\n{error_msg}" @@ -359,12 +358,10 @@ class ConvertModelException(OvmsTestException): class UploadModelsUnstableException(OvmsTestException): """Raised when upload/export restored models from backup.""" - pass class ReloadModelsUnstableException(OvmsTestException): """Raised when reload linked models from fallback (previous weeks).""" - pass class OVVPException(OvmsTestException): diff --git a/tests/functional/utils/http/client_auth/auth.py b/tests/functional/utils/http/client_auth/auth.py index a889dd3b5c..8e1ef47c63 100644 --- a/tests/functional/utils/http/client_auth/auth.py +++ b/tests/functional/utils/http/client_auth/auth.py @@ -286,7 +286,6 @@ class ClientAuthSsl(ClientAuthNoAuth): Class implemented to comply with coding standard. Authorisation is taken care by SSL certificates, no extra auth is needed. """ - pass class HTTPCookieAuth(AuthBase): @@ -541,27 +540,18 @@ def get(username: Union[HttpUser, str] = None, if auth_type == ClientAuthType.TOKEN_AUTH: return ClientAuthToken(auth_url, session, params) - - elif auth_type == ClientAuthType.HTTP_SESSION: + if auth_type == ClientAuthType.HTTP_SESSION: return ClientAuthSession(auth_url, session, params) - - elif auth_type == ClientAuthType.HTTP_BASIC: + if auth_type == ClientAuthType.HTTP_BASIC: return ClientAuthHttpBasic(auth_url, session, params) - - elif auth_type == ClientAuthType.TOKEN_NO_AUTH: + if auth_type == ClientAuthType.TOKEN_NO_AUTH: return ClientAuthTokenProvided(auth_url, session, params) - - elif auth_type == ClientAuthType.NO_AUTH: + if auth_type == ClientAuthType.NO_AUTH: return ClientAuthNoAuth(auth_url, session) - - elif auth_type == ClientAuthType.SSL: + if auth_type == ClientAuthType.SSL: return ClientAuthSsl(auth_url, session) - - elif auth_type == ClientAuthType.LOGIN_PAGE: + if auth_type == ClientAuthType.LOGIN_PAGE: return ClientAuthLoginPage(auth_url, session, params) - - elif auth_type == ClientAuthType.OAUTH2_PROXY_AUTH: + if auth_type == ClientAuthType.OAUTH2_PROXY_AUTH: return ClientAuthOAuth2Proxy(auth_url, session, params) - - else: - raise ClientAuthFactoryInvalidAuthTypeException(auth_type) + raise ClientAuthFactoryInvalidAuthTypeException(auth_type) diff --git a/tests/functional/utils/http/client_auth/base.py b/tests/functional/utils/http/client_auth/base.py index 3fe60eb430..151b61c1ab 100644 --- a/tests/functional/utils/http/client_auth/base.py +++ b/tests/functional/utils/http/client_auth/base.py @@ -69,7 +69,6 @@ def request_data(self) -> OrderedDict: def parse_params(self, params: dict) -> None: """params parser to pass configuration customizations""" - pass @property def auth_request_params(self) -> dict: diff --git a/tests/functional/utils/http/http_client_factory.py b/tests/functional/utils/http/http_client_factory.py index 8e750a7b4b..70669c7005 100644 --- a/tests/functional/utils/http/http_client_factory.py +++ b/tests/functional/utils/http/http_client_factory.py @@ -33,33 +33,23 @@ def get(cls, configuration: HttpClientConfiguration) -> HttpClient: if client_type == HttpClientType.TOKEN_AUTH: return cls._get_instance(configuration, ClientAuthType.TOKEN_AUTH) - - elif client_type == HttpClientType.SESSION_AUTH: + if client_type == HttpClientType.SESSION_AUTH: return cls._get_instance(configuration, ClientAuthType.HTTP_SESSION) - - elif client_type == HttpClientType.NO_AUTH: + if client_type == HttpClientType.NO_AUTH: return cls._get_instance(configuration, ClientAuthType.NO_AUTH) - - elif client_type == HttpClientType.K8S: + if client_type == HttpClientType.K8S: return cls._get_instance(configuration, ClientAuthType.TOKEN_NO_AUTH) - - elif client_type == HttpClientType.BROKER: + if client_type == HttpClientType.BROKER: return cls._get_instance(configuration, ClientAuthType.HTTP_BASIC) - - elif client_type == HttpClientType.BASIC_AUTH: + if client_type == HttpClientType.BASIC_AUTH: return cls._get_instance(configuration, ClientAuthType.HTTP_BASIC) - - elif client_type == HttpClientType.API: + if client_type == HttpClientType.API: return cls._get_instance(configuration, ClientAuthType.LOGIN_PAGE) - - elif client_type == HttpClientType.OAUTH2_PROXY_AUTH: + if client_type == HttpClientType.OAUTH2_PROXY_AUTH: return cls._get_instance(configuration, ClientAuthType.OAUTH2_PROXY_AUTH) - - elif client_type == HttpClientType.SSL: + if client_type == HttpClientType.SSL: return cls._get_instance(configuration, ClientAuthType.SSL) - - else: - raise HttpClientFactoryInvalidClientTypeException(client_type) + raise HttpClientFactoryInvalidClientTypeException(client_type) @classmethod def remove(cls, configuration: HttpClientConfiguration): diff --git a/tests/functional/utils/http/http_socket_wrapper.py b/tests/functional/utils/http/http_socket_wrapper.py index 9f4195acd3..0c48383b91 100644 --- a/tests/functional/utils/http/http_socket_wrapper.py +++ b/tests/functional/utils/http/http_socket_wrapper.py @@ -29,7 +29,9 @@ def __init__(self, host, port): self.host = host self.port = port - def send(self, method, path, body='', headers={}): + def send(self, method, path, body='', headers=None): + if headers is None: + headers = {} default_headers = {"Host": f"{self.host}:{self.port}", "Content-Length": str(len(body)), "Connection": 'close'} From 146fa8fb7f518778e8aaf2ea26e8236a570bae95 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 26 Aug 2026 13:02:57 +0200 Subject: [PATCH 06/13] pytlint fixes part 5 --- tests/functional/constants/ovms.py | 1 + tests/functional/constants/ovms_openai.py | 1 + tests/functional/constants/pipelines.py | 2 +- .../object_model/inference_helpers.py | 2 +- .../object_model/mediapipe_calculators.py | 8 +++--- tests/functional/object_model/ovms_config.py | 8 ++++-- .../functional/object_model/ovms_instance.py | 2 +- .../object_model/ovms_log_monitor.py | 6 ++-- tests/functional/object_model/ovms_params.py | 1 + .../object_model/package_manager.py | 3 +- .../python_custom_nodes.py | 1 + tests/functional/pylintrc | 28 ++++++++++--------- tests/functional/utils/context.py | 2 +- tests/functional/utils/core.py | 1 + tests/functional/utils/docker.py | 15 ++++------ tests/functional/utils/git_operations.py | 1 + tests/functional/utils/hooks.py | 3 +- tests/functional/utils/http/base.py | 1 + .../functional/utils/http/client_auth/auth.py | 5 ++-- .../utils/http/http_client_configuration.py | 2 +- tests/functional/utils/inference/capi.py | 2 +- .../utils/inference/communication/base.py | 1 - .../utils/inference/serving/base.py | 5 +--- .../functional/utils/inference/serving/kf.py | 2 +- .../utils/inference/serving/openai.py | 2 +- .../utils/inference/serving/triton.py | 1 + tests/functional/utils/log_monitor.py | 3 +- tests/functional/utils/numpy_loader.py | 1 + tests/functional/utils/process.py | 4 +-- .../unittests/test_manager.py | 1 + 30 files changed, 63 insertions(+), 52 deletions(-) diff --git a/tests/functional/constants/ovms.py b/tests/functional/constants/ovms.py index 5e417b18e5..2fad54a0ea 100644 --- a/tests/functional/constants/ovms.py +++ b/tests/functional/constants/ovms.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=unnecessary-lambda-assignment import os import re diff --git a/tests/functional/constants/ovms_openai.py b/tests/functional/constants/ovms_openai.py index 13b595e33a..7f3549f7f2 100644 --- a/tests/functional/constants/ovms_openai.py +++ b/tests/functional/constants/ovms_openai.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=arguments-differ from dataclasses import dataclass from typing import Union diff --git a/tests/functional/constants/pipelines.py b/tests/functional/constants/pipelines.py index 09fa7dc9fb..7b8560bf8c 100644 --- a/tests/functional/constants/pipelines.py +++ b/tests/functional/constants/pipelines.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,abstract-method import os from abc import abstractmethod diff --git a/tests/functional/object_model/inference_helpers.py b/tests/functional/object_model/inference_helpers.py index 89ca7f7db5..a94d7109bf 100644 --- a/tests/functional/object_model/inference_helpers.py +++ b/tests/functional/object_model/inference_helpers.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,no-member import base64 import json diff --git a/tests/functional/object_model/mediapipe_calculators.py b/tests/functional/object_model/mediapipe_calculators.py index 9d747183fd..236bfec6d8 100644 --- a/tests/functional/object_model/mediapipe_calculators.py +++ b/tests/functional/object_model/mediapipe_calculators.py @@ -151,10 +151,10 @@ def create_proto_header( input_streams = "" output_streams = "" if model is not None: - for i, model_input in enumerate(model.inputs, start=0): + for i, _model_input in enumerate(model.inputs, start=0): input_streams += f'input_stream: "{input_stream}_{i}" \n' - for i, model_output in enumerate(model.outputs, start=0): + for i, _model_output in enumerate(model.outputs, start=0): output_streams += f'output_stream: "{output_stream}_{i}" \n' else: if isinstance(input_stream, List): @@ -185,7 +185,7 @@ def create_input_output_streams(self, model, input_stream, output_stream): inputs = "" model_name = self.get_upper_model_name(model) - for (i, model_input), inp_stream in zip(enumerate(model.inputs, start=0), input_streams): + for (i, _model_input), inp_stream in zip(enumerate(model.inputs, start=0), input_streams): inp = f"{model_name}_INPUT_{i}" inputs += ( f'input_stream: "{inp}:{inp_stream}_{i}" \n' @@ -194,7 +194,7 @@ def create_input_output_streams(self, model, input_stream, output_stream): ) outputs = "" - for (i, model_output), out_stream in zip(enumerate(model.outputs, start=0), output_streams): + for (i, _model_output), out_stream in zip(enumerate(model.outputs, start=0), output_streams): out = f"{model_name}_OUTPUT_{i}" outputs += ( f'output_stream: "{out}:{out_stream}_{i}" \n' diff --git a/tests/functional/object_model/ovms_config.py b/tests/functional/object_model/ovms_config.py index 639620e6e5..3d1260d7ea 100644 --- a/tests/functional/object_model/ovms_config.py +++ b/tests/functional/object_model/ovms_config.py @@ -123,7 +123,7 @@ def save_without_encoding(config_path, config_dict: dict): @staticmethod def build( - models: List[ModelInfo] = [], + models: List[ModelInfo] = None, pipelines: List[Pipeline] = None, custom_nodes: List[CustomNode] = None, metrics_enable=MetricsPolicy.NotDefined, @@ -133,6 +133,8 @@ def build( use_subconfig=False, custom_graph_paths=None, ) -> dict: + if models is None: + models = [] config = OvmsConfig.build_ovms_config( models, pipelines, @@ -148,7 +150,7 @@ def build( @staticmethod def build_ovms_config( - models: List[ModelInfo] = [], + models: List[ModelInfo] = None, pipelines: List[Pipeline] = None, custom_nodes: List[CustomNode] = None, metrics_enable=MetricsPolicy.NotDefined, @@ -158,6 +160,8 @@ def build_ovms_config( use_subconfig=False, custom_graph_paths=None, ) -> dict: + if models is None: + models = [] if use_subconfig: config = {Config.MODEL_CONFIG_LIST: []} else: diff --git a/tests/functional/object_model/ovms_instance.py b/tests/functional/object_model/ovms_instance.py index b6f31ce45c..6bea863ee6 100644 --- a/tests/functional/object_model/ovms_instance.py +++ b/tests/functional/object_model/ovms_instance.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,no-member import os import random diff --git a/tests/functional/object_model/ovms_log_monitor.py b/tests/functional/object_model/ovms_log_monitor.py index 72b1fbf74c..1e5c420ee6 100644 --- a/tests/functional/object_model/ovms_log_monitor.py +++ b/tests/functional/object_model/ovms_log_monitor.py @@ -48,7 +48,7 @@ def ensure_contains_messages( str_set_to_find, break_msg_list=None, timeout=None, - callbacks=[], + callbacks=None, ovms_instance=None, all_messages=True, ): @@ -263,7 +263,7 @@ def models_unloaded(self, models, pipelines=None, timeout=None, ovms_instance=No self.ensure_contains_messages(msg_list, timeout=timeout, ovms_instance=ovms_instance) def models_loaded( - self, models, custom_msg_list=None, break_msg_list=None, timeout=None, callbacks=[], ovms_instance=None + self, models, custom_msg_list=None, break_msg_list=None, timeout=None, callbacks=None, ovms_instance=None ): if timeout is None: timeout = wait_for_messages_timeout @@ -312,7 +312,7 @@ def get_models_loading_time(self, models, is_reload): [OvmsMessages.MODEL_RELOADING.format(model.name)], raise_exception_if_not_found=True ) else: - found_messages, messages_to_find_vs_results_map = self.find_messages( + _, messages_to_find_vs_results_map = self.find_messages( [OvmsMessages.MODEL_LOADING.format(model.name, model.version, model.base_path)], raise_exception_if_not_found=True, ) diff --git a/tests/functional/object_model/ovms_params.py b/tests/functional/object_model/ovms_params.py index 9d5017508a..9ad424813a 100644 --- a/tests/functional/object_model/ovms_params.py +++ b/tests/functional/object_model/ovms_params.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import json from dataclasses import dataclass diff --git a/tests/functional/object_model/package_manager.py b/tests/functional/object_model/package_manager.py index 8ebf2e518e..37301dc780 100644 --- a/tests/functional/object_model/package_manager.py +++ b/tests/functional/object_model/package_manager.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import re from abc import ABC, abstractmethod @@ -176,7 +177,7 @@ def get_list_of_installed_packages(self, container_id): else: match = rpm_list_pkg_no_arch_regexp.match(pkg) assert match, f"Unable to parse package info: {pkg}" - name, version, release = match.groups() + name, version, _release = match.groups() arch = "noarch" detected_packages[name] = {"arch": arch, "version": version} diff --git a/tests/functional/object_model/python_custom_nodes/python_custom_nodes.py b/tests/functional/object_model/python_custom_nodes/python_custom_nodes.py index 4646889f53..ea2647de3f 100644 --- a/tests/functional/object_model/python_custom_nodes/python_custom_nodes.py +++ b/tests/functional/object_model/python_custom_nodes/python_custom_nodes.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member,abstract-method import numpy as np diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc index ca9f1004ec..c07945c4b2 100644 --- a/tests/functional/pylintrc +++ b/tests/functional/pylintrc @@ -76,7 +76,7 @@ ignore-paths= constants/ovms_messages.py, # W0511: constants/ovms_openai.py, constants/paths.py, - constants/pipelines.py, # (R0801: duplicate code with models.py) + tests/functional/constants/pipelines.py, # (R0801: duplicate code with models.py) constants/target_device_configuration.py, # W0105: String statement has no effect (pointless-string-statement) tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py, tests/functional/data/ovms_capi_wrapper/setup.py, @@ -112,44 +112,44 @@ ignore-paths= object_model/shape.py, object_model/test_environment.py, # R0205: (useless-object-inheritance) object_model/test_helpers.py, - utils/assertions.py, + tests/functional/utils/assertions.py, utils/context.py, utils/core.py, utils/docker.py, utils/git_operations.py, utils/hooks.py, utils/http/base.py, - utils/http/client_auth/auth.py, + tests/functional/utils/http/client_auth/auth.py, utils/http/client_auth/base.py, utils/http/http_client.py, utils/http/http_client_configuration.py, utils/http/http_client_factory.py, utils/http/http_session.py, utils/http/http_socket_wrapper.py, - utils/inference/capi.py, - tests/functional/utils/inference/communication/base.py, + tests/functional/utils/inference/capi.py, + utils/inference/communication/base.py, tests/functional/utils/inference/communication/grpc.py, tests/functional/utils/inference/communication/rest.py, utils/inference/inference_client_factory.py, utils/inference/serving/base.py, - utils/inference/serving/kf.py, + tests/functional/utils/inference/serving/kf.py, utils/inference/serving/openai.py, utils/inference/serving/tf.py, - utils/inference/serving/triton.py, + tests/functional/utils/inference/serving/triton.py, utils/log_monitor.py, utils/logger.py, utils/marks.py, - utils/numpy_loader.py, + tests/functional/utils/numpy_loader.py, utils/port_manager.py, utils/process.py, utils/reservation_manager/__init__.py, - utils/reservation_manager/__main__.py, - utils/reservation_manager/args.py, + tests/functional/utils/reservation_manager/__main__.py, + tests/functional/utils/reservation_manager/args.py, utils/reservation_manager/locker.py, - utils/reservation_manager/manager.py, + tests/functional/utils/reservation_manager/manager.py, utils/reservation_manager/manager_config.py, - utils/reservation_manager/runner.py, - utils/reservation_manager/unittests/test_manager.py, + tests/functional/utils/reservation_manager/runner.py, + tests/functional/utils/reservation_manager/unittests/test_manager.py, # Files or directories matching the regular expression patterns are skipped. # The regex matches against base names, not paths. The default value ignores @@ -529,6 +529,8 @@ disable= useless-suppression, # (I0021) deprecated-pragma, # (I0022) use-symbolic-message-instead, # (I0023) + broad-exception-caught, # (W0718, to be FIXED) + broad-exception-raised, # (W0719, to be FIXED) inconsistent-return-statements, # (R1710, to be FIXED) duplicate-code, # (R0801, to be FIXED) logging-fstring-interpolation, # (W1203, to be FIXED) diff --git a/tests/functional/utils/context.py b/tests/functional/utils/context.py index d0fa6299ca..2a18fe9844 100644 --- a/tests/functional/utils/context.py +++ b/tests/functional/utils/context.py @@ -52,7 +52,7 @@ def _cleanup_test_objects(self, object_list: list): try: self.logger.info(f"calling {item!s} to /get object to/ clean.") item = item() - except BaseException as exc: + except BaseException as exc: # pylint: disable=broad-exception-caught self.logger.exception(f"Cannot call on callable item {item!r}", exc_info=exc) continue if item is None: diff --git a/tests/functional/utils/core.py b/tests/functional/utils/core.py index f76c4b3a7d..2762b4336c 100644 --- a/tests/functional/utils/core.py +++ b/tests/functional/utils/core.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member,abstract-method import inspect import json diff --git a/tests/functional/utils/docker.py b/tests/functional/utils/docker.py index e87ed45d7e..e3a52378dc 100644 --- a/tests/functional/utils/docker.py +++ b/tests/functional/utils/docker.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,no-member import pprint import signal @@ -333,9 +333,9 @@ def get_status(self, status=None, timeout=None): def assert_status(self, status): current_status = self.get_status() assert current_status == status, ( - "Not expected status for container {} found. \n " - "Expected: {}, \n " - "received: {}".format(self.container.name, status, self.container.status) + f"Not expected status for container {self.container.name} found. \n " + f"Expected: {status}, \n " + f"received: {self.container.status}" ) return True @@ -385,12 +385,7 @@ def ensure_not_on_list( def __repr__(self): _id = self.container.id if self.container is not None else "" ports = pprint.pformat(self.container.ports) if self.container is not None else "" - return "<%s: %s%s>@%s" % ( - self.__class__.__name__, - self.id, - " (%s)" % _id, - "ports: %s." % ports, - ) + return f"<{self.__class__.__name__}: {self.id} ({_id})>@ports: {ports}." @classmethod def list_from_response(cls, rsp): diff --git a/tests/functional/utils/git_operations.py b/tests/functional/utils/git_operations.py index 3f75fdfd67..84b124c188 100644 --- a/tests/functional/utils/git_operations.py +++ b/tests/functional/utils/git_operations.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import os from pathlib import Path diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index 211c9f2345..c4bd4ff1a0 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import itertools import os @@ -647,7 +648,7 @@ def parametrize_plugin_config(metafunc): (device_type, plugin_config) for device_type in config.target_devices for plugin_config in args[0][get_base_device(device_type)] ] - ids_list = lambda i: get_ids_with_target_device(i, lambda x: "-".join(map(lambda y: "%s=%s" % y, x.items()))) + ids_list = lambda i: get_ids_with_target_device(i, lambda x: "-".join(map(lambda y: f"{y[0]}={y[1]}", x.items()))) metafunc.parametrize(f"{TARGET_DEVICE_PARAM_NAME}, {MarkTestParameters.PLUGIN_CONFIG}", params_list, ids=ids_list) diff --git a/tests/functional/utils/http/base.py b/tests/functional/utils/http/base.py index 9a22756f4e..818c780b40 100644 --- a/tests/functional/utils/http/base.py +++ b/tests/functional/utils/http/base.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member from enum import Enum diff --git a/tests/functional/utils/http/client_auth/auth.py b/tests/functional/utils/http/client_auth/auth.py index 8e1ef47c63..ea3145411c 100644 --- a/tests/functional/utils/http/client_auth/auth.py +++ b/tests/functional/utils/http/client_auth/auth.py @@ -381,8 +381,9 @@ def cookies_repr(self, indent: int): return "\n".join(cookies) + "\n" def login_hook(self, http_session: HttpSession) -> Callable[[Response], Response]: - state = dict(logging_hook_fn=0, redirects=0, - authorizations=0, authorizations_skipped=0) + state = { + "logging_hook_fn": 0, "redirects": 0, "authorizations": 0, "authorizations_skipped": 0 + } def login_hook_fn(initial_response: Response, *args, **kwargs) -> Response: logger.verbose(f"\nLogin hook for session: {str(id(http_session))[-4:]}." diff --git a/tests/functional/utils/http/http_client_configuration.py b/tests/functional/utils/http/http_client_configuration.py index bd440a6eea..42d603a5f6 100644 --- a/tests/functional/utils/http/http_client_configuration.py +++ b/tests/functional/utils/http/http_client_configuration.py @@ -108,7 +108,7 @@ def password(self): @property def as_dict(self) -> dict: - kwargs = dict() + kwargs = {} self.set_value(kwargs, "username", self.username) self.set_value(kwargs, "password", self.password) self.set_value(kwargs, "proxies", self.proxies) diff --git a/tests/functional/utils/inference/capi.py b/tests/functional/utils/inference/capi.py index 997568e669..5f77c091f2 100644 --- a/tests/functional/utils/inference/capi.py +++ b/tests/functional/utils/inference/capi.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,no-member import pytest diff --git a/tests/functional/utils/inference/communication/base.py b/tests/functional/utils/inference/communication/base.py index 73c3d13db8..7510f81fb0 100644 --- a/tests/functional/utils/inference/communication/base.py +++ b/tests/functional/utils/inference/communication/base.py @@ -34,7 +34,6 @@ def prepare_request(self, input_objects: dict, **kwargs): """ Abstract method for preparing the inference request. """ - pass @abc.abstractmethod def get_model_meta(self, timeout=60, version=None, update_model_info=True, model_name=None): diff --git a/tests/functional/utils/inference/serving/base.py b/tests/functional/utils/inference/serving/base.py index 0bd038fd1f..54b0aad40d 100644 --- a/tests/functional/utils/inference/serving/base.py +++ b/tests/functional/utils/inference/serving/base.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,no-member import abc @@ -30,14 +30,12 @@ def set_grpc_stubs(self): """ Assigns objects for inference purposes. """ - pass @abc.abstractmethod def create_inference(self): """ Assigns objects for inference purposes. """ - pass @abc.abstractmethod def predict(self, request): @@ -48,7 +46,6 @@ def get_rest_path(self, operation, model_version=None, model_name=None): """ REST path construction is dependent from serving used: (Tensorflow / KServe) """ - pass @abc.abstractmethod def get_inputs_outputs_from_response(self, response): diff --git a/tests/functional/utils/inference/serving/kf.py b/tests/functional/utils/inference/serving/kf.py index 8710466bc0..35e024eb0c 100644 --- a/tests/functional/utils/inference/serving/kf.py +++ b/tests/functional/utils/inference/serving/kf.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,no-member import enum import json diff --git a/tests/functional/utils/inference/serving/openai.py b/tests/functional/utils/inference/serving/openai.py index 078eef8fb3..d038c5bddd 100644 --- a/tests/functional/utils/inference/serving/openai.py +++ b/tests/functional/utils/inference/serving/openai.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# pylint: disable=unused-argument +# pylint: disable=unused-argument,abstract-method from dataclasses import dataclass from typing import Tuple, Union diff --git a/tests/functional/utils/inference/serving/triton.py b/tests/functional/utils/inference/serving/triton.py index c6f1d5d4b7..ae1d4c6592 100644 --- a/tests/functional/utils/inference/serving/triton.py +++ b/tests/functional/utils/inference/serving/triton.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import queue diff --git a/tests/functional/utils/log_monitor.py b/tests/functional/utils/log_monitor.py index 4914a64d69..61458d3bc7 100644 --- a/tests/functional/utils/log_monitor.py +++ b/tests/functional/utils/log_monitor.py @@ -129,12 +129,13 @@ def wait_for_messages( break_msg_list=None, raise_exception_if_not_found=True, timeout=None, - callbacks=[], + callbacks=None, ovms_instance=None, check_ovms_running=True, all_messages=False, ): break_msg_list = [] if break_msg_list is None else break_msg_list + callbacks = callbacks or [] if timeout is None: timeout = wait_for_messages_timeout diff --git a/tests/functional/utils/numpy_loader.py b/tests/functional/utils/numpy_loader.py index 124bdb3d2f..b80540c9b4 100644 --- a/tests/functional/utils/numpy_loader.py +++ b/tests/functional/utils/numpy_loader.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import os import re diff --git a/tests/functional/utils/process.py b/tests/functional/utils/process.py index 3dab1f1ee7..0131a2a4a4 100644 --- a/tests/functional/utils/process.py +++ b/tests/functional/utils/process.py @@ -254,9 +254,9 @@ def kill(self, force=False, timeout=30): for child_proc in child_processes: try: child_proc.terminate() - except psutil.NoSuchProcess as e: + except psutil.NoSuchProcess: pass - except psutil.AccessDenied as e: + except psutil.AccessDenied: self._kill_by_shell(child_proc.pid, end_time=end_time, force=force, sudo=True) _, alive = psutil.wait_procs([child_proc], diff --git a/tests/functional/utils/reservation_manager/unittests/test_manager.py b/tests/functional/utils/reservation_manager/unittests/test_manager.py index d09ac86ef0..3c7276177b 100644 --- a/tests/functional/utils/reservation_manager/unittests/test_manager.py +++ b/tests/functional/utils/reservation_manager/unittests/test_manager.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# pylint: disable=no-member import pytest From 0ee23037d6c44c8acd7cea2238c90af9881d2c69 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Fri, 28 Aug 2026 06:56:07 +0200 Subject: [PATCH 07/13] add test_embeddings.py, test_rerank.py, test_text_generation.py --- ci/build_test_OnCommit.groovy | 2 +- docs/developer_guide.md | 19 ++- tests/functional/config.py | 41 +++--- tests/functional/conftest.py | 4 +- tests/functional/constants/ovms.py | 1 + tests/functional/constants/ovms_images.py | 18 +-- tests/functional/constants/paths.py | 2 + tests/functional/models/models_datasets.py | 47 ++++++- tests/functional/models/models_generative.py | 60 ++++++++ tests/functional/models/models_library.py | 66 ++++++++- .../object_model/inference_helpers.py | 55 ++------ tests/functional/pylintrc | 88 ++++-------- tests/functional/test_embeddings.py | 115 +++++++++++++++ tests/functional/test_rerank.py | 132 ++++++++++++++++++ tests/functional/test_text_generation.py | 106 ++++++++++++++ tests/functional/utils/hooks.py | 38 +++++ tests/functional/utils/ov_hf_downloader.py | 2 +- tests/models/README.md | 2 +- tests/requirements.txt | 3 + 19 files changed, 643 insertions(+), 158 deletions(-) create mode 100644 tests/functional/test_embeddings.py create mode 100644 tests/functional/test_rerank.py create mode 100644 tests/functional/test_text_generation.py diff --git a/ci/build_test_OnCommit.groovy b/ci/build_test_OnCommit.groovy index 7737f36203..8daa7341d4 100644 --- a/ci/build_test_OnCommit.groovy +++ b/ci/build_test_OnCommit.groovy @@ -71,7 +71,7 @@ pipeline { if (git_diff =~ /(\n|^)client/) { client_test_needed = "true" } - if (git_diff =~ /(\n|^)tests\/functional/) { + if (git_diff =~ /(\n|^)(tests\/functional|tests\/requirements\.txt)/) { functional_tests_changed = "true" } if (git_diff =~ /(\n|^)(demos\/common\/export_models\/|prepare_llm_models\.sh$)/) { diff --git a/docs/developer_guide.md b/docs/developer_guide.md index 34c9c9a250..77d63be84b 100644 --- a/docs/developer_guide.md +++ b/docs/developer_guide.md @@ -174,16 +174,13 @@ make test_functional - Configuration options are : -| Variable | Description | -| :--- | :---- | -| `IMAGE` | Docker image name for the tests.| -| `TEST_DIR_CACHE`| Location from which models and test data are downloaded.| -| `TEST_DIR` | Location to which models and test data are copied during tests.| -| `TEST_DIR_CLEANUP` | Set to `True` to remove the directory under `TEST_DIR` after the tests.| -| `LOG_LEVEL` | The log level.| -| `BUILD_LOGS` | Path to save artifacts.| -| `START_CONTAINER_COMMAND` | The command to start the OpenVINO Model Storage container.| -| `CONTAINER_LOG_LINE` | The log line in the container that confirms the container started properly.| +| Variable | Description | +| :--- |:----------------------------------------------------------------------------| +| `TT_OVMS_IMAGE_NAME` | Docker image name for the tests. | +| `TT_LOGGING_LEVEL` | The log level for tests. | +| `TT_LOGGING_LEVEL_OVMS` | The log level for OVMS. | +| `BUILD_LOGS` | Path to save artifacts. | +| `START_CONTAINER_COMMAND` | The command to start the OpenVINO Model Storage container. | 2. Add any configuration variables to the command line in this format : @@ -464,7 +461,7 @@ Use OpenVINO Model Server build image because it installs the necessary tools. 4. Run a test in this terminal. Change `TEST_PATH` to point to the test you want to debug: ```bash - make test_functional TEST_PATH=tests/functional/test_batching.py::TestBatchModelInference::test_run_inference_rest IMAGE=openvino/model_server-build:latest + make test_functional TEST_PATH=tests/functional/test_embeddings.py::TestEmbeddings::test_on_commit_embeddings_endpoints TT_OVMS_IMAGE_NAME=openvino/model_server-build:latest ``` 5. Open a second terminal. diff --git a/tests/functional/config.py b/tests/functional/config.py index 94cc755d56..5d798cff5c 100644 --- a/tests/functional/config.py +++ b/tests/functional/config.py @@ -17,6 +17,8 @@ import os import re + +from collections import defaultdict from pathlib import Path from tests.functional.constants.os_type import OsType @@ -62,9 +64,6 @@ def get_uses_mapping(): """TEST_DIR - location where models and test data should be copied from TEST_DIR_CACHE and deleted after tests""" test_dir = os.environ.get("TEST_DIR", f"/tmp/{generate_test_object_name(prefix='ovms_models')}") -"""TEST_DIR_CACHE - location where models and test data should be downloaded to and serve as cache for TEST_DIR""" -test_dir_cache = os.environ.get("TEST_DIR_CACHE", "/tmp/ovms_models_cache") - """ TT_OVMS_C_REPO_PATH - path to ovms-c repository. Can be relative or absolute. """ ovms_c_repo_path = get_path("TT_OVMS_C_REPO_PATH", get_path("PWD", "./")) @@ -105,17 +104,20 @@ def get_uses_mapping(): """ TT_WIN_PY_VERSION - Python version for virtualenv on Windows OS """ windows_python_version = os.environ.get("TT_WIN_PY_VERSION", "3.12") -""" TT_DOCKER_REGISTRY - Docker registry""" +""" TT_DOCKER_REGISTRY - Docker registry """ docker_registry = os.environ.get("TT_DOCKER_REGISTRY", None) """ OVMS_CPP_DOCKER_IMAGE """ -ovms_cpp_docker_image = os.environ.get("OVMS_CPP_DOCKER_IMAGE", None) - -""" TT_OVMS_IMAGE_NAME """ -ovms_image = os.environ.get("TT_OVMS_IMAGE_NAME", None) +ovms_cpp_docker_image = os.environ.get("OVMS_CPP_DOCKER_IMAGE", "openvino/model_server") """ OVMS_CPP_IMAGE_TAG - tag of OVMS image to test (compatible with build parameter) """ -ovms_image_tag = os.environ.get("OVMS_CPP_IMAGE_TAG", None) +ovms_image_tag = os.environ.get("OVMS_CPP_IMAGE_TAG", "latest") + +""" TT_DEFAULT_OVMS_IMAGE_TAG - default value of ovms image tag (based on OS) """ +ovms_image_tag_dict = defaultdict(lambda: ovms_image_tag) + +""" TT_OVMS_IMAGE_NAME - full image name (name + tag) """ +ovms_image = os.environ.get("TT_OVMS_IMAGE_NAME", None) """ TT_OVMS_TEST_IMAGE_NAME - image name for cpu extensions and custom nodes """ ovms_test_image_name = os.environ.get("TT_OVMS_TEST_IMAGE_NAME", None) @@ -131,22 +133,9 @@ def get_uses_mapping(): """START_CONTAINER_COMMAND - command to start ovms container""" start_container_command = os.environ.get("START_CONTAINER_COMMAND", "") -"""CONTAINER_LOG_LINE - log line to check in container""" -# For multiple log lines, pass them separated with ':' -container_log_line = os.environ.get("CONTAINER_LOG_LINE", "Started model manager thread") -container_log_line = container_log_line.split(":") - """OVMS_BINARY_PATH - path to ovms binary file; when specified, tests are executed against provided binary.""" ovms_binary_path = os.environ.get("OVMS_BINARY_PATH", None) -"""LOG_LEVEL - set log level """ -log_level = os.environ.get("LOG_LEVEL", "INFO") - -path_to_mount = os.path.join(test_dir, "saved_models") -os.makedirs(path_to_mount, exist_ok=True) - -path_to_mount_cache = os.path.join(test_dir_cache, "saved_models") - """ TT_MINIO_IMAGE_NAME - Docker image for Minio""" minio_image = os.environ.get( "TT_MINIO_IMAGE_NAME", @@ -431,3 +420,11 @@ def get_ovms_types(): """ TT_HUGGINGFACE_TOKEN - huggingface token value. Env var takes priority, then file. """ huggingface_token = os.environ.get("TT_HUGGINGFACE_TOKEN") or get_token_value(huggingface_token_file_path, "") + +"""TT_ON_COMMIT_TESTS - False -> api-on-commit tests are not run, + True -> api-on-commit tests are run, default: True """ +run_on_commit_tests = get_bool("TT_ON_COMMIT_TESTS", True) + +"""TT_RUN_REGRESSION_TESTS - False -> api-regression tests are not run, + True -> api-regression tests are run, default: False """ +run_regression_tests = get_bool("TT_RUN_REGRESSION_TESTS", False) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 8fc805e772..c96effad26 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -14,9 +14,9 @@ # limitations under the License. # -import pytest import random import sys +import pytest from tests.functional.config import enable_pytest_plugins, pytest_keyword_filter, machine_is_reserved_for_test_session from tests.functional.constants.components import OvmsComponents @@ -34,8 +34,6 @@ if enable_pytest_plugins: - raise NotImplementedError("OVMS tests not enabled") - pytest_plugins = [ # pylint: disable=unreachable "tests.functional.fixtures.ovms", "tests.functional.fixtures.server", diff --git a/tests/functional/constants/ovms.py b/tests/functional/constants/ovms.py index 2fad54a0ea..bbc33d65f3 100644 --- a/tests/functional/constants/ovms.py +++ b/tests/functional/constants/ovms.py @@ -245,6 +245,7 @@ class CurrentOvmsType: TEST_RUN_WORKER_ARGUMENT = "test_run_reporters" TMP_REPOS_DIR_ARGUMENT = "tmp_repos_dir" CURRENT_TARGET_DEVICE_DICT_ARGUMENT = "current_target_device_dict" +ENDPOINT_PARAM_NAME = "endpoint" class Config: diff --git a/tests/functional/constants/ovms_images.py b/tests/functional/constants/ovms_images.py index ca91441b1d..6cbee13da6 100644 --- a/tests/functional/constants/ovms_images.py +++ b/tests/functional/constants/ovms_images.py @@ -26,6 +26,7 @@ ovms_cpp_docker_image, ovms_image, ovms_image_tag, + ovms_image_tag_dict, ovms_test_image_name, target_devices, ) @@ -93,19 +94,12 @@ def _get_os_type_and_version(cls): NGINX = "nginx" -DEFAULT_OVMS_IMAGE_NAME = "openvino/model_server" DEFAULT_OVMS_IMAGE_SUFFIXES = { NGINX: "-nginx-mtls", TargetDevice.GPU: "-gpu", TargetDevice.NPU: "-gpu", } -DEFAULT_OVMS_IMAGE_TAG = { - OsType.Ubuntu22: "ubuntu22_main", - OsType.Ubuntu24: "ubuntu24_main", - OsType.Redhat: "redhat_main", -} - def calculate_ovms_image_suffix(target_device): if is_nginx_mtls: @@ -148,14 +142,12 @@ def calculate_ovms_image_name(target_device=None, base_os=OsType.Ubuntu22): image_name = f"{image_name}{calculate_ovms_image_suffix(target_device)}" image_tag = calculate_ovms_image_tag(image_tag, base_os, base_os_list) else: - if ovms_cpp_docker_image: - image_name = ovms_cpp_docker_image - elif docker_registry is not None: - image_name = f"{docker_registry}/{DEFAULT_OVMS_IMAGE_NAME}" + if docker_registry is not None: + image_name = f"{docker_registry}/{ovms_cpp_docker_image}" else: - image_name = DEFAULT_OVMS_IMAGE_NAME + image_name = ovms_cpp_docker_image image_name = f"{image_name}{calculate_ovms_image_suffix(target_device)}" - image_tag = ovms_image_tag if ovms_image_tag else DEFAULT_OVMS_IMAGE_TAG[base_os] + image_tag = ovms_image_tag if ovms_image_tag else ovms_image_tag_dict[base_os] image_tag = calculate_ovms_image_tag(image_tag, base_os, base_os_list) return f"{image_name}:{image_tag}" diff --git a/tests/functional/constants/paths.py b/tests/functional/constants/paths.py index deebd6ac92..8f7291d251 100644 --- a/tests/functional/constants/paths.py +++ b/tests/functional/constants/paths.py @@ -59,6 +59,8 @@ class Paths: LLM_EXPORT_MODELS_REQUIREMENTS = os.path.join(LLM_EXPORT_MODELS_DIR, "requirements.txt") LLM_EXPORT_MODELS_SCRIPT = os.path.join(LLM_EXPORT_MODELS_DIR, "export_model.py") + OVMS_C_IMAGES = os.path.join(config.ovms_c_repo_path, "demos", "common", "static", "images") + @staticmethod def CAPI_WRAPPER_PACKAGE_CONTENT_PATH(base_os): return os.path.join(config.c_api_wrapper_dir, base_os, "ovms") diff --git a/tests/functional/models/models_datasets.py b/tests/functional/models/models_datasets.py index 26e1bdc428..23a9028511 100644 --- a/tests/functional/models/models_datasets.py +++ b/tests/functional/models/models_datasets.py @@ -19,6 +19,7 @@ # pylint: disable=too-many-positional-arguments # pylint: disable=unused-argument +import base64 import json import os import re @@ -33,6 +34,7 @@ from tests.functional.config import binary_io_images_path, datasets_path from tests.functional.constants.ovms import Ovms +from tests.functional.constants.paths import Paths from tests.functional.utils.inference.serving.openai import ChatCompletionsApi from tests.functional.utils.numpy_loader import prepare_data @@ -70,9 +72,9 @@ def to_str(self): class NumPyDataset(ModelDataset): - def __init__(self, *data_path): + def __init__(self, *data_path, data_src_path=None): self.name = data_path[0] - self.data_path = os.path.join(datasets_path, *data_path) + self.data_path = data_src_path if data_src_path is not None else os.path.join(datasets_path, *data_path) class LanguageModelDataset(ModelDataset): @@ -123,6 +125,47 @@ def create_data(self, tmp_file_location, shape, img_format): return {} +class ZebraImageDataset(NumPyDataset): + def __init__(self): + file_name = "zebra.jpeg" + super().__init__(file_name, data_src_path=os.path.join(Paths.OVMS_C_IMAGES, file_name)) + + +class VisionLanguageModelImageDataset(ZebraImageDataset): + pass + + +class VisionLanguageModelDataset(LargeLanguageModelDataset): + image_datasets = [ZebraImageDataset] + user_content_text = "Describe what is in the picture." + user_content_image_url = {"url": f"data:image/jpeg;base64,CONVERT_IMAGE_0"} + user_content = [ + { + ChatCompletionsApi.CONTENT_TYPE: ChatCompletionsApi.CONTENT_TYPE_TEXT, + ChatCompletionsApi.CONTENT_TYPE_TEXT: user_content_text, + }, { + ChatCompletionsApi.CONTENT_TYPE: ChatCompletionsApi.CONTENT_TYPE_IMAGE_URL, + ChatCompletionsApi.CONTENT_TYPE_IMAGE_URL: user_content_image_url, + } + ] + user_data = [ChatCompletionsApi.ROLE_USER, user_content] + input_data = [user_data] + + def __init__(self, data_sample=0): + input_data_str = json.dumps(self.input_data) + for i, image_dataset in enumerate(self.image_datasets): + convert_image_text = self.convert_image(image_dataset().data_path) + input_data_str = input_data_str.replace(f"CONVERT_IMAGE_{i}", convert_image_text) + self.input_data = json.loads(input_data_str) + self.default_input_data = self.input_data + + @staticmethod + def convert_image(image_path): + with open(image_path, "rb") as file: + base64_image = base64.b64encode(file.read()).decode("utf-8") + return base64_image + + class FeatureExtractionModelDataset(LargeLanguageModelDataset): input_data_1 = "That is a happy person." input_data_2 = "That is a very happy person." diff --git a/tests/functional/models/models_generative.py b/tests/functional/models/models_generative.py index 0ba49cae1f..cab85ff693 100644 --- a/tests/functional/models/models_generative.py +++ b/tests/functional/models/models_generative.py @@ -31,6 +31,7 @@ LargeLanguageModelDataset, RerankModelDataset, SingleMessageLanguageModelDataset, + VisionLanguageModelDataset, ) @@ -123,6 +124,15 @@ def get_default_dataset(self): return LargeLanguageModelDataset +@dataclass +class VisionLanguageModel(GenerativeModel): + is_vision_language: bool = True + + @staticmethod + def get_default_dataset(): + return VisionLanguageModelDataset + + @dataclass class FeatureExtractionModel(GenerativeModel): use_subconfig: bool = True @@ -180,9 +190,59 @@ def prepare_input_data(self, batch_size=None, input_key=None, dataset=None, inpu return input_data +@dataclass +class BgeRerankerBaseFp16OvHf(GenerativeModelHuggingFace, RerankModel): + name: str = "OpenVINO/bge-reranker-base-fp16-ov" + precision: str = "FP16" + is_local: bool = True + + +@dataclass +class Gemma34bItInt4OvHf(GenerativeModelHuggingFace, VisionLanguageModel): + name: str = "OpenVINO/gemma-3-4b-it-int4-ov" + is_local: bool = True + + +@dataclass +class Gemma34bItInt4CwOvHf(GenerativeModelHuggingFace, VisionLanguageModel): + name: str = "OpenVINO/gemma-3-4b-it-int4-cw-ov" + is_local: bool = True + + +@dataclass +class LFM25350MInt8OvHf(GenerativeModelHuggingFace, LargeLanguageModel): + name: str = "OpenVINO/LFM2.5-350M-int8-ov" + precision: str = "INT8" + tool_parser: str = "lfm2" + is_agentic: bool = True + gorilla_patch_name: str = "ovms-model" + pipeline_type: str = "LM" + is_local: bool = True + + +@dataclass +class Phi35MiniInstructInt4CwOvHf(GenerativeModelHuggingFace, LargeLanguageModel): + name: str = "OpenVINO/Phi-3.5-mini-instruct-int4-cw-ov" + is_local: bool = True + + @dataclass class Qwen3Embedding06BFp16OvHf(GenerativeModelHuggingFace, FeatureExtractionModel): name: str = "OpenVINO/Qwen3-Embedding-0.6B-fp16-ov" precision: str = "FP16" pooling: str = "LAST" is_local: bool = True + + +@dataclass +class Qwen3Reranker06BFp16OvHf(GenerativeModelHuggingFace, RerankModel): + name: str = "OpenVINO/Qwen3-Reranker-0.6B-fp16-ov" + precision: str = "FP16" + is_local: bool = True + + +@dataclass +class Qwen3Reranker06BSeqClsFp16OvHf(GenerativeModelHuggingFace, RerankModel): + name: str = "OpenVINO/Qwen3-Reranker-0.6B-seq-cls-fp16-ov" + precision: str = "FP16" + is_local: bool = True diff --git a/tests/functional/models/models_library.py b/tests/functional/models/models_library.py index a095ad7caf..996a6e6c3a 100644 --- a/tests/functional/models/models_library.py +++ b/tests/functional/models/models_library.py @@ -14,14 +14,76 @@ # limitations under the License. # -from tests.functional.models.models_generative import Qwen3Embedding06BFp16OvHf +from collections import defaultdict + +from tests.functional.constants.target_device import TargetDevice +from tests.functional.models.models_generative import ( + BgeRerankerBaseFp16OvHf, + Gemma34bItInt4OvHf, + Gemma34bItInt4CwOvHf, + LFM25350MInt8OvHf, + Phi35MiniInstructInt4CwOvHf, + Qwen3Embedding06BFp16OvHf, + Qwen3Reranker06BFp16OvHf, + Qwen3Reranker06BSeqClsFp16OvHf, +) class ModelsLibrary: @property - def various_feature_extraction_models(self): + def various_mini_large_language_models(self): + return defaultdict( + list, + { + TargetDevice.CPU: [LFM25350MInt8OvHf], + TargetDevice.GPU: [LFM25350MInt8OvHf], + TargetDevice.NPU: [Phi35MiniInstructInt4CwOvHf], + }, + ) + + @property + def various_mini_vision_language_models(self): + return defaultdict( + list, + { + TargetDevice.CPU: [Gemma34bItInt4OvHf], + TargetDevice.GPU: [Gemma34bItInt4OvHf], + TargetDevice.NPU: [Gemma34bItInt4CwOvHf], + }, + ) + + @property + def various_large_and_vision_language_models_on_commit(self): + return defaultdict( + list, + { + TargetDevice.CPU: + self.various_mini_large_language_models[TargetDevice.CPU] + + self.various_mini_vision_language_models[TargetDevice.CPU], + TargetDevice.GPU: + self.various_mini_large_language_models[TargetDevice.GPU] + + self.various_mini_vision_language_models[TargetDevice.GPU], + TargetDevice.NPU: + self.various_mini_large_language_models[TargetDevice.NPU] + + self.various_mini_vision_language_models[TargetDevice.NPU], + }, + ) + + @property + def various_feature_extraction_models_on_commit(self): return [Qwen3Embedding06BFp16OvHf] + @property + def various_rerank_models_on_commit(self): + return [BgeRerankerBaseFp16OvHf] + + @property + def various_rerank_models(self): + return [ + Qwen3Reranker06BFp16OvHf, + Qwen3Reranker06BSeqClsFp16OvHf, + ] + ModelsLib = ModelsLibrary() # pylint: disable=invalid-name diff --git a/tests/functional/object_model/inference_helpers.py b/tests/functional/object_model/inference_helpers.py index a94d7109bf..9c5297b0de 100644 --- a/tests/functional/object_model/inference_helpers.py +++ b/tests/functional/object_model/inference_helpers.py @@ -94,54 +94,19 @@ def create_client( self, api_type, port, batch_size=Ovms.BATCHSIZE, ovsa_certs=None, model_version=None, client_type=None ): ovsa_certs = ovsa_certs if ovsa_certs is not None else OvsaCerts.default_certs - if client_type == KFS: - assert 0, "Please check flow for KFS client" - kfs_api_type = InferenceClientKFS if api_type == InferenceClientTFS else InferenceRestClientKFS - inference_client = self.create_kfs_client(kfs_api_type, port) - else: - inference_client = api_type( - port=port, - model_name=self.model.name, - batch_size=batch_size, - input_names=list(self.model.inputs.keys()), - output_names=list(self.model.outputs.keys()), - model_meta_from_serving=False, - ssl_certificates=ovsa_certs, - model_version=model_version, - ) + inference_client = api_type( + port=port, + model_name=self.model.name, + batch_size=batch_size, + input_names=list(self.model.inputs.keys()), + output_names=list(self.model.outputs.keys()), + model_meta_from_serving=False, + ssl_certificates=ovsa_certs, + model_version=model_version, + ) inference_client._model = self.model return inference_client - def create_client_and_data(self, inference_request, random_data=False): - if inference_request.client_type == KFS: - # This should be included into KserveWrapper, please correct calling test not to use `create_client_and_data` - assert False, "Please correct it" - kfs_api_type = ( - InferenceClientKFS if inference_request.api_type == InferenceClientTFS else InferenceRestClientKFS - ) - port = inference_request.get_port() - inference_client = self.create_kfs_client(kfs_api_type, port) - else: - inference_client = self.create_client( - inference_request.api_type, - inference_request.get_port(), - inference_request.batch_size, - client_type=inference_request.client_type, - model_version=inference_request.model_version, - ) - if inference_request is not None and inference_request.dataset: - input_data = inference_request.load_data() - else: - input_data = self.model.prepare_input_data(inference_request.batch_size, random_data=random_data) - return inference_client, input_data - - def create_kfs_client(self, api_type, port): - kfs_api_client = api_type(port, model_name=self.model.name, batch_size=self.model.batch_size) - kfs_api_client.model = self.model - kfs_api_client.port = port - kfs_api_client.model_name = self.model.name - return kfs_api_client - @dataclass(frozen=False) class InferenceRequest: diff --git a/tests/functional/pylintrc b/tests/functional/pylintrc index c07945c4b2..e9f618072d 100644 --- a/tests/functional/pylintrc +++ b/tests/functional/pylintrc @@ -69,15 +69,10 @@ fail-under=10 # format. Because '\\' represents the directory delimiter on Windows systems, # it can't be used as an escape character. ignore-paths= - config.py, - constants/metrics.py, - constants/ovms.py, - constants/ovms_images.py, - constants/ovms_messages.py, # W0511: - constants/ovms_openai.py, - constants/paths.py, + tests/functional/constants/metrics.py, + tests/functional/constants/ovms_images.py, + tests/functional/constants/paths.py, tests/functional/constants/pipelines.py, # (R0801: duplicate code with models.py) - constants/target_device_configuration.py, # W0105: String statement has no effect (pointless-string-statement) tests/functional/data/ovms_capi_wrapper/ovms_autopxd.py, tests/functional/data/ovms_capi_wrapper/setup.py, tests/functional/data/python_custom_nodes/incrementer/incrementer.py, @@ -88,66 +83,45 @@ ignore-paths= tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_multiple_use_of_valid_outputs.py, tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_loopback_return_instead_of_yield.py, tests/functional/data/python_custom_nodes/ovms_corrupted/python_model_writing_to_loopback_output_in_execute.py, - fixtures/*, - object_model/cpu_extension.py, # C0103: Attribute name doesn't conform to naming style (invalid-name) - object_model/custom_loader.py, - object_model/custom_node.py, - object_model/dmesg_log_monitor.py, - object_model/inference_helpers.py, - object_model/mediapipe_calculators.py, - object_model/ovms_binary.py, - object_model/ovms_capi.py, - object_model/ovms_command.py, - object_model/ovms_config.py, # (R0801: duplicate code with ovms_mapping_config.py) - object_model/ovms_docker.py, - object_model/ovms_info.py, - object_model/ovms_instance.py, - object_model/ovms_log_monitor.py, - object_model/ovms_mapping_config.py, # (R0801: duplicate code with ovms_config.py) - object_model/ovms_params.py, - object_model/ovsa.py, - object_model/package_manager.py, - object_model/python_custom_nodes/python_custom_nodes.py, - object_model/resource_monitor.py, - object_model/shape.py, - object_model/test_environment.py, # R0205: (useless-object-inheritance) - object_model/test_helpers.py, + tests/functional/fixtures/ovms.py, + tests/functional/fixtures/server.py, + tests/functional/object_model/cpu_extension.py, # C0103: Attribute name doesn't conform to naming style (invalid-name) + tests/functional/object_model/custom_node.py, + tests/functional/object_model/dmesg_log_monitor.py, + tests/functional/object_model/inference_helpers.py, + tests/functional/object_model/mediapipe_calculators.py, + tests/functional/object_model/ovms_binary.py, + tests/functional/object_model/ovms_capi.py, + tests/functional/object_model/ovms_config.py, # (R0801: duplicate code with ovms_mapping_config.py) + tests/functional/object_model/ovms_docker.py, + tests/functional/object_model/ovms_info.py, + tests/functional/object_model/ovms_instance.py, + tests/functional/object_model/ovms_log_monitor.py, + tests/functional/object_model/package_manager.py, + tests/functional/object_model/python_custom_nodes/python_custom_nodes.py, + tests/functional/object_model/resource_monitor.py, + tests/functional/object_model/shape.py, + tests/functional/object_model/test_helpers.py, tests/functional/utils/assertions.py, - utils/context.py, - utils/core.py, - utils/docker.py, - utils/git_operations.py, - utils/hooks.py, - utils/http/base.py, + tests/functional/utils/core.py, + tests/functional/utils/docker.py, + tests/functional/utils/git_operations.py, + tests/functional/utils/hooks.py, tests/functional/utils/http/client_auth/auth.py, - utils/http/client_auth/base.py, - utils/http/http_client.py, - utils/http/http_client_configuration.py, - utils/http/http_client_factory.py, - utils/http/http_session.py, - utils/http/http_socket_wrapper.py, tests/functional/utils/inference/capi.py, - utils/inference/communication/base.py, tests/functional/utils/inference/communication/grpc.py, tests/functional/utils/inference/communication/rest.py, - utils/inference/inference_client_factory.py, - utils/inference/serving/base.py, tests/functional/utils/inference/serving/kf.py, - utils/inference/serving/openai.py, - utils/inference/serving/tf.py, tests/functional/utils/inference/serving/triton.py, - utils/log_monitor.py, - utils/logger.py, - utils/marks.py, + tests/functional/utils/log_monitor.py, + tests/functional/utils/logger.py, + tests/functional/utils/marks.py, tests/functional/utils/numpy_loader.py, - utils/port_manager.py, - utils/process.py, - utils/reservation_manager/__init__.py, + tests/functional/utils/port_manager.py, + tests/functional/utils/process.py, tests/functional/utils/reservation_manager/__main__.py, tests/functional/utils/reservation_manager/args.py, - utils/reservation_manager/locker.py, tests/functional/utils/reservation_manager/manager.py, - utils/reservation_manager/manager_config.py, tests/functional/utils/reservation_manager/runner.py, tests/functional/utils/reservation_manager/unittests/test_manager.py, diff --git a/tests/functional/test_embeddings.py b/tests/functional/test_embeddings.py new file mode 100644 index 0000000000..3d1fc3aaf6 --- /dev/null +++ b/tests/functional/test_embeddings.py @@ -0,0 +1,115 @@ +# +# Copyright (c) 2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# pylint: disable=too-many-positional-arguments + +import pytest + +from tests.functional.models.models_library import ModelsLib +from tests.functional.constants.components import OvmsComponents +from tests.functional.constants.ovms_openai import EncodingFormatValues +from tests.functional.constants.ovms_type import OvmsType +from tests.functional.constants.requirements import Requirements +from tests.functional.constants.target_device import TargetDevice +from tests.functional.constants.target_device_configuration import nginx_mtls_not_supported_for_test +from tests.functional.object_model.inference_helpers import run_llm_inference +from tests.functional.utils.context import Context +from tests.functional.utils.generative_ai.utils import calculate_generative_test_timeout, GenerativeAIUtils +from tests.functional.utils.inference.serving.openai import OpenAIWrapper +from tests.functional.utils.logger import get_logger, step +from tests.functional.utils.test_framework import ( + skip_if_language_models_not_enabled, + skip_if_mediapipe_disabled, +) + +logger = get_logger(__name__) + + +@pytest.mark.priority_high +@pytest.mark.components(OvmsComponents.OVMS) +@pytest.mark.reqids(Requirements.embeddings_endpoint, Requirements.openai_api) +@pytest.mark.ovms_types_supported_for_test( + OvmsType.DOCKER, + OvmsType.DOCKER_CMD_LINE, + OvmsType.BINARY, + OvmsType.BINARY_DOCKER, +) +@skip_if_language_models_not_enabled() +@nginx_mtls_not_supported_for_test() +@skip_if_mediapipe_disabled() +class TestEmbeddings: + + @staticmethod + def run_embeddings_endpoints_test( + context: Context, model_type, openai_rest_api_type, endpoint, encoding_format, input_data_type + ): + model, result, port, request_params = GenerativeAIUtils.prepare_resources( + context, + model_type, + openai_rest_api_type, + endpoint, + encoding_format=encoding_format, + ) + + step("Run simple inference") + run_llm_inference( + model, + openai_rest_api_type, + port, + endpoint, + input_data_type=input_data_type, + request_parameters=request_params, + ) + + GenerativeAIUtils.unload_model_and_verify( + model, + result, + port, + openai_rest_api_type, + endpoint, + request_params + ) + + @pytest.mark.api_on_commit + @pytest.mark.devices_supported_for_test(TargetDevice.CPU, TargetDevice.GPU, TargetDevice.NPU) + @pytest.mark.model_type(ModelsLib.various_feature_extraction_models_on_commit) + @pytest.mark.parametrize("endpoint", [OpenAIWrapper.EMBEDDINGS]) + @pytest.mark.parametrize("encoding_format", EncodingFormatValues.values(), ids=lambda x: f"encoding_format={x}") + @pytest.mark.parametrize("input_data_type", ["list", "string"], ids=lambda x: f"input_data_type={x}") + @pytest.mark.timeout(calculate_generative_test_timeout(480)) + def test_on_commit_embeddings_endpoints( + self, context: Context, model_type, openai_rest_api_type, endpoint, encoding_format, input_data_type + ): + """ + Description: + Execute single inference with LLM type model using embeddings endpoint. + + Input data: + - Language model (feature extraction) type + + Expected results: + OVMS will properly load language model and execute inference. + + Steps: + 1. Prepare language model instance + 2. Start OVMS + 3. Run simple inference + 4. Unload model + 5. Verify model is unreachable + """ + self.run_embeddings_endpoints_test( + context, model_type, openai_rest_api_type, endpoint, encoding_format, input_data_type + ) diff --git a/tests/functional/test_rerank.py b/tests/functional/test_rerank.py new file mode 100644 index 0000000000..fd9569990d --- /dev/null +++ b/tests/functional/test_rerank.py @@ -0,0 +1,132 @@ +# +# Copyright (c) 2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# pylint: disable=too-many-positional-arguments + +import pytest + +from cohere import NotFoundError as CohereNotFoundError + +from tests.functional.models.models_library import ModelsLib +from tests.functional.constants.components import OvmsComponents +from tests.functional.constants.ovms_type import OvmsType +from tests.functional.constants.requirements import Requirements +from tests.functional.constants.target_device import TargetDevice +from tests.functional.constants.target_device_configuration import nginx_mtls_not_supported_for_test +from tests.functional.object_model.inference_helpers import run_llm_inference +from tests.functional.utils.context import Context +from tests.functional.utils.generative_ai.utils import calculate_generative_test_timeout, GenerativeAIUtils +from tests.functional.utils.inference.serving.cohere import CohereWrapper +from tests.functional.utils.logger import get_logger, step +from tests.functional.utils.test_framework import ( + skip_if_language_models_not_enabled, + skip_if_mediapipe_disabled, +) + +logger = get_logger(__name__) + + +@pytest.mark.priority_high +@pytest.mark.components(OvmsComponents.OVMS) +@pytest.mark.reqids(Requirements.rerank_endpoint, Requirements.openai_api) +@pytest.mark.ovms_types_supported_for_test( + OvmsType.DOCKER, + OvmsType.DOCKER_CMD_LINE, + OvmsType.BINARY, + OvmsType.BINARY_DOCKER, +) +@skip_if_language_models_not_enabled() +@nginx_mtls_not_supported_for_test() +@skip_if_mediapipe_disabled() +class TestRerank: + + @staticmethod + def run_rerank_endpoint_test(context: Context, model_type, cohere_rest_api_type, endpoint): + model, result, port, request_params = GenerativeAIUtils.prepare_resources( + context, + model_type, + cohere_rest_api_type, + endpoint, + ) + + step("Run simple inference") + run_llm_inference( + model, + cohere_rest_api_type, + port, + endpoint, + request_parameters=request_params, + ) + + GenerativeAIUtils.unload_model_and_verify( + model, + result, + port, + cohere_rest_api_type, + endpoint, + request_params, + error_type=CohereNotFoundError, + ) + + @pytest.mark.api_on_commit + @pytest.mark.devices_supported_for_test(TargetDevice.CPU, TargetDevice.GPU) + @pytest.mark.model_type(ModelsLib.various_rerank_models_on_commit) + @pytest.mark.parametrize("endpoint", [CohereWrapper.RERANK]) + @pytest.mark.timeout(calculate_generative_test_timeout(480)) + def test_on_commit_rerank_endpoints(self, context: Context, model_type, cohere_rest_api_type, endpoint): + """ + Description: + Execute single inference with LLM type model. + + Input data: + - Language model type + + Expected results: + OVMS will properly load language model and execute inference + + Steps: + 1. Prepare language model instance + 2. Start OVMS + 3. Run simple inference + 4. Unload model + 5. Verify model is unreachable + """ + self.run_rerank_endpoint_test(context, model_type, cohere_rest_api_type, endpoint) + + @pytest.mark.api_regression + @pytest.mark.devices_supported_for_test(TargetDevice.CPU, TargetDevice.GPU) + @pytest.mark.model_type(ModelsLib.various_rerank_models) + @pytest.mark.parametrize("endpoint", [CohereWrapper.RERANK]) + @pytest.mark.timeout(calculate_generative_test_timeout(480)) + def test_regression_rerank_endpoints(self, context: Context, model_type, cohere_rest_api_type, endpoint): + """ + Description: + Execute single inference with LLM type model. + + Input data: + - Language model type + + Expected results: + OVMS will properly load language model and execute inference + + Steps: + 1. Prepare language model instance + 2. Start OVMS + 3. Run simple inference + 4. Unload model + 5. Verify model is unreachable + """ + self.run_rerank_endpoint_test(context, model_type, cohere_rest_api_type, endpoint) diff --git a/tests/functional/test_text_generation.py b/tests/functional/test_text_generation.py new file mode 100644 index 0000000000..5e5f2ae81a --- /dev/null +++ b/tests/functional/test_text_generation.py @@ -0,0 +1,106 @@ +# +# Copyright (c) 2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# pylint: disable=too-many-positional-arguments + +import pytest + +from tests.functional.models.models_library import ModelsLib +from tests.functional.constants.components import OvmsComponents +from tests.functional.constants.ovms_openai import ( + MaxTokensValues, + TemperatureValues, +) +from tests.functional.constants.ovms_type import OvmsType +from tests.functional.constants.requirements import Requirements +from tests.functional.constants.target_device import TargetDevice +from tests.functional.constants.target_device_configuration import nginx_mtls_not_supported_for_test +from tests.functional.object_model.inference_helpers import run_llm_inference +from tests.functional.utils.context import Context +from tests.functional.utils.generative_ai.utils import calculate_generative_test_timeout, GenerativeAIUtils +from tests.functional.utils.inference.serving.openai import OpenAIWrapper +from tests.functional.utils.logger import get_logger, step +from tests.functional.utils.test_framework import ( + skip_if_language_models_not_enabled, + skip_if_mediapipe_disabled, +) + +logger = get_logger(__name__) + + +@pytest.mark.priority_high +@pytest.mark.components(OvmsComponents.OVMS) +@pytest.mark.reqids(Requirements.rerank_endpoint, Requirements.openai_api) +@pytest.mark.ovms_types_supported_for_test( + OvmsType.DOCKER, + OvmsType.DOCKER_CMD_LINE, + OvmsType.BINARY, + OvmsType.BINARY_DOCKER, +) +@skip_if_language_models_not_enabled() +@nginx_mtls_not_supported_for_test() +@skip_if_mediapipe_disabled() +class TestTextGeneration: + + @pytest.mark.api_on_commit + @pytest.mark.devices_supported_for_test(TargetDevice.CPU, TargetDevice.GPU, TargetDevice.NPU) + @pytest.mark.model_type(ModelsLib.various_large_and_vision_language_models_on_commit) + @pytest.mark.parametrize("endpoint", OpenAIWrapper.AVAILABLE_TEXT_GENERATION_ENDPOINTS) + @pytest.mark.parametrize("stream", [True, False], ids=lambda x: f"stream={x}") + @pytest.mark.parametrize("max_tokens", [MaxTokensValues.DEFAULT], ids=lambda x: f"max_tokens={x}") + @pytest.mark.parametrize("temperature", [TemperatureValues.TEST_DEFAULT], ids=lambda x: f"temperature={x}") + @pytest.mark.timeout(calculate_generative_test_timeout(480)) + def test_on_commit_llm_text_generation_endpoints( + self, context: Context, model_type, openai_rest_api_type, endpoint, stream, max_tokens, temperature + ): + """ + Description: + Execute single inference with LLM/VLM type model. + + Input data: + - Language model type/ Vision Language model type + + Expected results: + OVMS will properly load language model and execute inference + + Steps: + 1. Prepare language model instance + 2. Start OVMS + 3. Run simple inference + 4. Unload model + 5. Verify model is unreachable + """ + model, result, port, request_params = GenerativeAIUtils.prepare_resources( + context, + model_type, + openai_rest_api_type, + endpoint, + stream=stream, + max_tokens=max_tokens, + temperature=temperature, + ) + + step("Run simple inference") + run_llm_inference( + model, + openai_rest_api_type, + port, + endpoint, + validate_outputs_ttr=False, + request_parameters=request_params, + ) + + GenerativeAIUtils.unload_model_and_verify(model, result, port, openai_rest_api_type, endpoint, request_params) diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index c4bd4ff1a0..846f493e6d 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -33,6 +33,7 @@ from tests.functional import config from tests.functional.config import ( + airplane_mode, build_test_image, c_api_wrapper_dir, cleanup_env_on_startup, @@ -59,11 +60,16 @@ from tests.functional.constants.os_type import get_host_os, OsType, UBUNTU from tests.functional.constants.os_version import os_type_to_base_image_binary_docker from tests.functional.constants.ovms import ( + API_TYPE_PARAM_NAME, BASE_OS_PARAM_NAME, + CLOUD_TYPE_PARAM_NAME, CURRENT_TARGET_DEVICE_DICT_ARGUMENT, + ENDPOINT_PARAM_NAME, + MODEL_TYPE_PARAM_NAME, OVMS_TYPE_PARAM_NAME, TARGET_DEVICE_PARAM_NAME, TMP_REPOS_DIR_ARGUMENT, + USES_CONFIG_PARAM_NAME, USES_MAPPING_PARAM_NAME, ) from tests.functional.constants.ovms_images import ( @@ -93,6 +99,7 @@ from tests.functional.utils.core import TmpDir from tests.functional.utils.docker import DockerClient, DockerContainer, DOCKER_CONTAINER_TMP_PATH from tests.functional.utils.download import wget_file +from tests.functional.utils.inference.serving.openai import OpenAIWrapper from tests.functional.utils.environment_info import EnvironmentInfo from tests.functional.utils.helpers import get_base_device from tests.functional.utils.logger import get_logger @@ -491,6 +498,9 @@ def download_docker_images(): def download_resources_master(): + if airplane_mode: + print("Skipping downloading required resources") + return print("Download required resources") download_models() download_docker_images() @@ -1012,18 +1022,46 @@ def update_parent_markers(item, marker_types): item.own_markers.append(components) +def generic_test_deselect(item): + """ + Pytest test cases are generated as Cartesian products of all given arguments. + Same of parameters combinations are incompatible or contradictory and cannot be applied. + + In this function check generic @pytest.mark.parametrize / @fixture symbols from `item`. + """ + target_device = item.callspec.params.get(TARGET_DEVICE_PARAM_NAME, None) + ovms_type = item.callspec.params.get(OVMS_TYPE_PARAM_NAME, None) + api_type = item.callspec.params.get(API_TYPE_PARAM_NAME, None) + base_os = item.callspec.params.get(BASE_OS_PARAM_NAME, None) + use_config = item.callspec.params.get(USES_CONFIG_PARAM_NAME, None) + cloud_type = item.callspec.params.get(CLOUD_TYPE_PARAM_NAME, None) + model_type = item.callspec.params.get(MODEL_TYPE_PARAM_NAME, None) + use_mapping = item.callspec.params.get(USES_MAPPING_PARAM_NAME, None) + endpoint = item.callspac.params.get(ENDPOINT_PARAM_NAME, None) + + # Disable completions endpoint for VLM models + if model_type.is_vision_language and endpoint == OpenAIWrapper.COMPLETIONS: + return True + + return False + + def deselect(item, test_type, required_marker_ids, excluded_marker_ids): # Validate different scenarios where test should be deselected from execution during `collect` stage. if isinstance(item, Function): if test_type is None: raise RuntimeError("Test do not have test_type: " + item.name) + if generic_test_deselect(item): + return True + if required_marker_ids: for required_marker_id_list in required_marker_ids: if _is_test_marker_id_is_matched_with_id(item, required_marker_id_list): # make sure that item is not deselected by other marker return deselect_by_excluded_marker_ids(item, excluded_marker_ids) return True + if excluded_marker_ids: return deselect_by_excluded_marker_ids(item, excluded_marker_ids) diff --git a/tests/functional/utils/ov_hf_downloader.py b/tests/functional/utils/ov_hf_downloader.py index ab61475b38..a9d18f79d8 100644 --- a/tests/functional/utils/ov_hf_downloader.py +++ b/tests/functional/utils/ov_hf_downloader.py @@ -50,7 +50,7 @@ def check_and_update_hf_model(self): print(f"No files to update for model: {self.model_name}") return False - print(f"Download OVHf model: {self.model_name}") + print(f"Download OpenVINO HuggingFace model: {self.model_name}") staging_path = self.model_local_path + "_staging" if os.path.exists(staging_path): remove_dir_tree(staging_path) diff --git a/tests/models/README.md b/tests/models/README.md index bcf9113eeb..10e0372689 100644 --- a/tests/models/README.md +++ b/tests/models/README.md @@ -5,7 +5,7 @@ ```bash git clone https://github.com/openvinotoolkit/model_server.git cd model_server/tests/models -pip3 install -r requirements.txt +pip3 install -r ../requirements.txt ``` ## Model incrementing an input tensor diff --git a/tests/requirements.txt b/tests/requirements.txt index d8e312c724..b723d7c87e 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -25,4 +25,7 @@ requests-toolbelt==1.0.0 retry==0.9.2 setuptools==83.0.0 soundfile==0.14.0 +tensorboard==2.20.0 +tensorflow==2.21.0 +tensorflow-serving-api==2.20.0 tritonclient[all]==2.69.0 From 85787e4e2c9b93586be64e65fd6c5b44a7406d89 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Fri, 28 Aug 2026 13:14:11 +0200 Subject: [PATCH 08/13] image settings updates --- Makefile | 2 +- docs/developer_guide.md | 15 ++++++----- tests/functional/config.py | 2 +- tests/functional/constants/ovms_images.py | 6 +++-- tests/functional/models/models_library.py | 31 ++++++++--------------- 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index aebb6bbdf8..daec01d6f4 100644 --- a/Makefile +++ b/Makefile @@ -600,7 +600,7 @@ test_throughput_dummy_model: venv @docker rm --force $(OVMS_CPP_CONTAINER_NAME) test_functional: venv - @. $(ACTIVATE); pytest --json=report.json -v -s $(TEST_PATH) + @export OVMS_CPP_DOCKER_IMAGE=$(OVMS_CPP_DOCKER_IMAGE) && export OVMS_CPP_IMAGE_TAG=$(OVMS_CPP_IMAGE_TAG);. $(ACTIVATE); pytest --json=report.json -v -s $(TEST_PATH) test_python_clients: @echo "Prepare docker image" diff --git a/docs/developer_guide.md b/docs/developer_guide.md index 77d63be84b..44ecd0eb9e 100644 --- a/docs/developer_guide.md +++ b/docs/developer_guide.md @@ -174,13 +174,14 @@ make test_functional - Configuration options are : -| Variable | Description | -| :--- |:----------------------------------------------------------------------------| -| `TT_OVMS_IMAGE_NAME` | Docker image name for the tests. | -| `TT_LOGGING_LEVEL` | The log level for tests. | -| `TT_LOGGING_LEVEL_OVMS` | The log level for OVMS. | -| `BUILD_LOGS` | Path to save artifacts. | -| `START_CONTAINER_COMMAND` | The command to start the OpenVINO Model Storage container. | +| Variable | Description | +|:--------------------------|:-----------------------------------------------------------| +| `OVMS_CPP_DOCKER_IMAGE` | Docker image name for the tests. | +| `OVMS_CPP_IMAGE_TAG` | Docker image tag for the tests. | +| `TT_LOGGING_LEVEL` | The log level for tests. | +| `TT_LOGGING_LEVEL_OVMS` | The log level for OVMS. | +| `BUILD_LOGS` | Path to save artifacts. | +| `START_CONTAINER_COMMAND` | The command to start the OpenVINO Model Storage container. | 2. Add any configuration variables to the command line in this format : diff --git a/tests/functional/config.py b/tests/functional/config.py index 5d798cff5c..87f66f2bca 100644 --- a/tests/functional/config.py +++ b/tests/functional/config.py @@ -122,7 +122,7 @@ def get_uses_mapping(): """ TT_OVMS_TEST_IMAGE_NAME - image name for cpu extensions and custom nodes """ ovms_test_image_name = os.environ.get("TT_OVMS_TEST_IMAGE_NAME", None) -""" TT_FORCE_USE_OVMS_IMAGE - force to use TT_OVMS_IMAGE_NAME """ +""" TT_FORCE_USE_OVMS_IMAGE - force to use given image parameters (skip automatic suffix updates) """ force_use_ovms_image = get_bool("TT_FORCE_USE_OVMS_IMAGE", False) """ TT_OVMS_C_RELEASE_ARTIFACTS_PATH - path to current release artifacts """ diff --git a/tests/functional/constants/ovms_images.py b/tests/functional/constants/ovms_images.py index 6cbee13da6..9e6eb05ef8 100644 --- a/tests/functional/constants/ovms_images.py +++ b/tests/functional/constants/ovms_images.py @@ -134,9 +134,9 @@ def calculate_ovms_image_name(target_device=None, base_os=OsType.Ubuntu22): ct.target_device = target_device - if force_use_ovms_image and ovms_image: - return ovms_image if ovms_image: + if force_use_ovms_image: + return ovms_image image_name = re.sub("|".join(DEFAULT_OVMS_IMAGE_SUFFIXES.values()), "", ovms_image.split(":")[0]) image_tag = ovms_image.split(":")[1] image_name = f"{image_name}{calculate_ovms_image_suffix(target_device)}" @@ -146,6 +146,8 @@ def calculate_ovms_image_name(target_device=None, base_os=OsType.Ubuntu22): image_name = f"{docker_registry}/{ovms_cpp_docker_image}" else: image_name = ovms_cpp_docker_image + if force_use_ovms_image: + return f"{image_name}:{ovms_image_tag}" image_name = f"{image_name}{calculate_ovms_image_suffix(target_device)}" image_tag = ovms_image_tag if ovms_image_tag else ovms_image_tag_dict[base_os] image_tag = calculate_ovms_image_tag(image_tag, base_os, base_os_list) diff --git a/tests/functional/models/models_library.py b/tests/functional/models/models_library.py index 996a6e6c3a..525d2e78c9 100644 --- a/tests/functional/models/models_library.py +++ b/tests/functional/models/models_library.py @@ -17,16 +17,7 @@ from collections import defaultdict from tests.functional.constants.target_device import TargetDevice -from tests.functional.models.models_generative import ( - BgeRerankerBaseFp16OvHf, - Gemma34bItInt4OvHf, - Gemma34bItInt4CwOvHf, - LFM25350MInt8OvHf, - Phi35MiniInstructInt4CwOvHf, - Qwen3Embedding06BFp16OvHf, - Qwen3Reranker06BFp16OvHf, - Qwen3Reranker06BSeqClsFp16OvHf, -) +from tests.functional.models import models_generative as mg class ModelsLibrary: @@ -36,9 +27,9 @@ def various_mini_large_language_models(self): return defaultdict( list, { - TargetDevice.CPU: [LFM25350MInt8OvHf], - TargetDevice.GPU: [LFM25350MInt8OvHf], - TargetDevice.NPU: [Phi35MiniInstructInt4CwOvHf], + TargetDevice.CPU: [mg.LFM25350MInt8OvHf], + TargetDevice.GPU: [mg.LFM25350MInt8OvHf], + TargetDevice.NPU: [mg.Phi35MiniInstructInt4CwOvHf], }, ) @@ -47,9 +38,9 @@ def various_mini_vision_language_models(self): return defaultdict( list, { - TargetDevice.CPU: [Gemma34bItInt4OvHf], - TargetDevice.GPU: [Gemma34bItInt4OvHf], - TargetDevice.NPU: [Gemma34bItInt4CwOvHf], + TargetDevice.CPU: [mg.Gemma34bItInt4OvHf], + TargetDevice.GPU: [mg.Gemma34bItInt4OvHf], + TargetDevice.NPU: [mg.Gemma34bItInt4CwOvHf], }, ) @@ -72,17 +63,17 @@ def various_large_and_vision_language_models_on_commit(self): @property def various_feature_extraction_models_on_commit(self): - return [Qwen3Embedding06BFp16OvHf] + return [mg.Qwen3Embedding06BFp16OvHf] @property def various_rerank_models_on_commit(self): - return [BgeRerankerBaseFp16OvHf] + return [mg.BgeRerankerBaseFp16OvHf] @property def various_rerank_models(self): return [ - Qwen3Reranker06BFp16OvHf, - Qwen3Reranker06BSeqClsFp16OvHf, + mg.Qwen3Reranker06BFp16OvHf, + mg.Qwen3Reranker06BSeqClsFp16OvHf, ] From 3c9b40a21d0a784f8dd19b5ca954cd9e91e45446 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Tue, 1 Sep 2026 19:14:52 +0200 Subject: [PATCH 09/13] update requirements.txt --- tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index b723d7c87e..99661a5b3f 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -4,7 +4,7 @@ dataclasses-json==0.6.7 distro==1.9.0 docker==7.1.0 filelock==3.29.4 -GitPython==3.1.55 +GitPython==3.1.58 grpcio==1.67.1 Jinja2==3.1.6 jiwer>=4.0.0 From bdee610ac4d138ac71a7d4686670c809b6d8990e Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 2 Sep 2026 12:53:01 +0200 Subject: [PATCH 10/13] updates --- tests/functional/utils/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index 846f493e6d..3773f04471 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -1037,7 +1037,7 @@ def generic_test_deselect(item): cloud_type = item.callspec.params.get(CLOUD_TYPE_PARAM_NAME, None) model_type = item.callspec.params.get(MODEL_TYPE_PARAM_NAME, None) use_mapping = item.callspec.params.get(USES_MAPPING_PARAM_NAME, None) - endpoint = item.callspac.params.get(ENDPOINT_PARAM_NAME, None) + endpoint = item.callspec.params.get(ENDPOINT_PARAM_NAME, None) # Disable completions endpoint for VLM models if model_type.is_vision_language and endpoint == OpenAIWrapper.COMPLETIONS: From dad271520d9ece171308be0fbdd204bac28c080b Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 2 Sep 2026 14:03:40 +0200 Subject: [PATCH 11/13] groovy for functional tests --- ci/functional_tests_pytest.groovy | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 ci/functional_tests_pytest.groovy diff --git a/ci/functional_tests_pytest.groovy b/ci/functional_tests_pytest.groovy new file mode 100644 index 0000000000..f9dda02a12 --- /dev/null +++ b/ci/functional_tests_pytest.groovy @@ -0,0 +1,62 @@ +pipeline { + agent none + options { + timeout(time: 3, unit: 'HOURS') + } + parameters { + string( + name: 'TARGET_HOST', + defaultValue: 'ovms_icelake', + description: 'Worker label to run functional tests on' + ) + string( + name: 'CORE_BRANCH', + defaultValue: 'main', + description: 'ovms-c branch to use for this test run' + ) + string( + name: 'PYTEST_PARAMS', + defaultValue: 'tests/functional', + description: 'Pytest target(s) and options, e.g. tests/functional or tests/functional/test_something.py -k smoke' + ) + text( + name: 'TEST_PARAMETERS', + defaultValue: '', + description: 'Extra shell environment assignments to apply before pytest, one per line. Example: TT_TEST=21' + ) + } + stages { + stage('Run functional tests') { + agent { + label "${params.TARGET_HOST}" + } + steps { + script { + if (!(params.TARGET_HOST ==~ /[a-zA-Z0-9_.-]+/)) { + error "Invalid TARGET_HOST '${params.TARGET_HOST}'. Allowed characters: letters, digits, dot, underscore, hyphen." + } + def envAssignments = params.TEST_PARAMETERS + .readLines() + .findAll { line -> !line.trim().isEmpty() } + .collect { line -> line.trim() } + .join(' ') + def buildDir = "${env.WORKSPACE}/job-${env.BUILD_NUMBER}" + ws(buildDir) { + checkout([$class: 'GitSCM', branches: [[name: "*/${params.CORE_BRANCH}"]], userRemoteConfigs: [[url: scm.userRemoteConfigs[0].url, credentialsId: scm.userRemoteConfigs[0].credentialsId]]]) + sh """ + set -eux + export CORE_BRANCH='${params.CORE_BRANCH}' + test -d .venv || python3 -m venv .venv + . .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install -r tests/requirements.txt + ${envAssignments} pytest ${params.PYTEST_PARAMS} --junitxml=pytest-functional.xml + """ + junit allowEmptyResults: true, testResults: 'pytest-functional.xml' + archiveArtifacts allowEmptyArchive: true, artifacts: 'pytest-functional.xml,test_log/**,tests/functional/test_log_build/**' + } + } + } + } + } +} From 6ee2bc0df5627a3a01a846369d1833e43a4536dd Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 2 Sep 2026 14:12:48 +0200 Subject: [PATCH 12/13] updates --- tests/functional/utils/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/utils/hooks.py b/tests/functional/utils/hooks.py index 3773f04471..b96198a253 100644 --- a/tests/functional/utils/hooks.py +++ b/tests/functional/utils/hooks.py @@ -1040,7 +1040,7 @@ def generic_test_deselect(item): endpoint = item.callspec.params.get(ENDPOINT_PARAM_NAME, None) # Disable completions endpoint for VLM models - if model_type.is_vision_language and endpoint == OpenAIWrapper.COMPLETIONS: + if model_type is not None and model_type.is_vision_language and endpoint == OpenAIWrapper.COMPLETIONS: return True return False From 10ab8fceee2ec10ede3f05d60adac33ab6d0a6a3 Mon Sep 17 00:00:00 2001 From: Natalia Groza Date: Wed, 2 Sep 2026 16:06:42 +0200 Subject: [PATCH 13/13] updates groovy --- ci/functional_tests_pytest.groovy | 46 +++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/ci/functional_tests_pytest.groovy b/ci/functional_tests_pytest.groovy index f9dda02a12..456efc9f1b 100644 --- a/ci/functional_tests_pytest.groovy +++ b/ci/functional_tests_pytest.groovy @@ -19,6 +19,42 @@ pipeline { defaultValue: 'tests/functional', description: 'Pytest target(s) and options, e.g. tests/functional or tests/functional/test_something.py -k smoke' ) + string( + name: 'TT_XDIST_WORKERS', + defaultValue: '4', + description: 'Number of pytest-xdist workers to use for parallel execution' + ) + string( + name: 'TT_TARGET_DEVICE', + defaultValue: 'CPU', + description: 'Target device(s) for OVMS tests, e.g. CPU or CPU,GPU,NPU' + ) + + string( + name: 'TT_OVMS_IMAGE_NAME', + defaultValue: 'openvino/model_server:latest', + description: 'Full OVMS image name, e.g. openvino/model_server:latest. Empty means config default (None)' + ) + booleanParam( + name: 'TT_OVMS_IMAGE_LOCAL', + defaultValue: false, + description: 'Whether the OVMS image is available only locally. Default matches config.py: False' + ) + string( + name: 'TT_LOGGING_LEVEL_OVMS', + defaultValue: 'INFO', + description: 'OVMS container log level. Default matches config.py: INFO' + ) + booleanParam( + name: 'TT_ON_COMMIT_TESTS', + defaultValue: true, + description: 'Run on-commit tests. Default matches config.py: True' + ) + booleanParam( + name: 'TT_RUN_REGRESSION_TESTS', + defaultValue: false, + description: 'Run regression tests. Default matches config.py: False' + ) text( name: 'TEST_PARAMETERS', defaultValue: '', @@ -45,12 +81,18 @@ pipeline { checkout([$class: 'GitSCM', branches: [[name: "*/${params.CORE_BRANCH}"]], userRemoteConfigs: [[url: scm.userRemoteConfigs[0].url, credentialsId: scm.userRemoteConfigs[0].credentialsId]]]) sh """ set -eux - export CORE_BRANCH='${params.CORE_BRANCH}' test -d .venv || python3 -m venv .venv . .venv/bin/activate python -m pip install --upgrade pip python -m pip install -r tests/requirements.txt - ${envAssignments} pytest ${params.PYTEST_PARAMS} --junitxml=pytest-functional.xml + export TT_XDIST_WORKERS='${params.TT_XDIST_WORKERS}' + export TT_TARGET_DEVICE='${params.TT_TARGET_DEVICE}' + export TT_OVMS_IMAGE_NAME='${params.TT_OVMS_IMAGE_NAME}' + export TT_OVMS_IMAGE_LOCAL='${params.TT_OVMS_IMAGE_LOCAL}' + export TT_LOGGING_LEVEL_OVMS='${params.TT_LOGGING_LEVEL_OVMS}' + export TT_ON_COMMIT_TESTS='${params.TT_ON_COMMIT_TESTS}' + export TT_RUN_REGRESSION_TESTS='${params.TT_RUN_REGRESSION_TESTS}' + ${envAssignments} pytest ${params.PYTEST_PARAMS} -n ${params.TT_XDIST_WORKERS} --junitxml=pytest-functional.xml """ junit allowEmptyResults: true, testResults: 'pytest-functional.xml' archiveArtifacts allowEmptyArchive: true, artifacts: 'pytest-functional.xml,test_log/**,tests/functional/test_log_build/**'