fix: update logger library v2

This commit is contained in:
Alex Lisitsyn
2026-03-02 09:23:52 +00:00
parent 3ce5ac01a6
commit 9d9fece0f9
+200 -117
View File
@@ -9,7 +9,9 @@ import sys
from datetime import datetime
from enum import Enum
from statistics import mean
from typing import Any, Callable, Dict, Match, Optional, Tuple, List
from typing import Any, Callable, Dict, Generator, Optional, Tuple, List
from re import Match
import numpy as np
import matplotlib.pyplot as plt
from dataclasses import dataclass
@@ -49,6 +51,7 @@ OBJ_ID = 2
OBJ_ADDRESS = 1
CID = 2
PARAM_NAME = 3
PARAM_VAL = 4
PARAM_SUCCESS = "success"
PARAM_FAIL = "fail"
@@ -84,6 +87,7 @@ class MbParameter:
status,
cid,
response_time,
value
) -> None:
self.name: str = name
self.instance_address: bytes = instance_address
@@ -92,13 +96,45 @@ class MbParameter:
self.status: str = status
self.cid: bytes = cid
self.response_time: Optional[int] = response_time
self.value: Optional[bytes] = value
def __repr__(self) -> str:
if self.obj_tag == MASTER_TAG:
return f"Parameter name:{self.name}, Obj type: {self.obj_tag}, Object ID:{self.instance_address!r}, Transaction time:{self.transaction_timestamp}, Status:{self.status}, Cid:{self.cid}, Master Response time:{self.response_time}"
return f"Parameter name:{self.name}, Obj tag: {self.obj_tag}, Object ID:{self.instance_address!r}, Transaction time:{self.transaction_timestamp}, Status:{self.status}, Cid:{self.cid}, Value: {self.value}, Response time:{self.response_time}"
else:
return f"Parameter name:{self.name}, Obj type: {self.obj_tag}, Object ID:{self.instance_address}, Transaction time:{self.transaction_timestamp}, Status:{self.status}"
return f"Parameter name:{self.name}, Obj tag: {self.obj_tag}, Object ID:{self.instance_address}, Transaction time:{self.transaction_timestamp}, Status:{self.status}"
def get_name(self) -> str:
"""Retrieve parameter name (string)."""
return self.name
def get_inst(self) -> bytes:
"""Retrieve parameter instance address (bytes)."""
return self.instance_address
def get_timestamp(self) -> int:
"""Retrieve parameter transaction timestamp (int)."""
return self.transaction_timestamp
def get_tag(self) -> str:
"""Retrieve parameter tag (string)."""
return self.obj_tag
def get_status(self) -> str:
"""Retrieve parameter status (string)."""
return self.status
def get_cid(self) -> bytes:
"""Retrieve parameter cid (bytes)."""
return self.cid
def get_response_time(self) -> Optional[int]:
"""Retrieve parameter response time (ms) or None."""
return self.response_time
def get_value(self) -> Optional[bytes]:
"""Retrieve parameter value (bytes) or a sentinel b\"N/A\"."""
return self.value if self.value is not None else b"N/A"
class MbObject:
def __init__(self, tag, id, object_creation_timestamp) -> None:
@@ -108,7 +144,7 @@ class MbObject:
self.parameters: List[MbParameter] = []
self.parameter_count: int = 0
def __repr__(self):
def __repr__(self) -> str:
return f"Obj Tag: {self.tag}, Obj ID: {self.id}, Creation Timestamp: {self.object_creation_timestamp}"
def add_parameter(
@@ -119,9 +155,10 @@ class MbObject:
tag: str,
status: str,
cid: bytes,
value: Optional[bytes]
) -> MbParameter:
"""The function add to list master or slave parameters"""
parameter = MbParameter(
parameter: MbParameter = MbParameter(
name,
instance_address,
int(transaction_timestamp.decode("ascii")),
@@ -129,6 +166,7 @@ class MbObject:
status,
cid,
None,
value
)
self.parameters.append(parameter)
self.parameter_count += 1
@@ -150,27 +188,28 @@ class MbObject:
class ModbusTestDut(IdfDut):
TEST_IP_PROMPT = r"Waiting IP([0-9]{1,2}) from stdin:"
TEST_IP_PROMPT = r"Waiting IP\(([0-9]{1,2})\) from stdin:"
TEST_IP_ADDRESS_REGEXP = r"I \([0-9]+\) example_[a-z]+: [A-Za-z\-]* IPv4 [A-Za-z\"_:\s]*address: ([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})"
TEST_APP_NAME = r"I \([0-9]+\) [a-z_]+: Project name:\s+([_a-z]*)"
TEST_EXPECT_STR_TIMEOUT = 120
TEST_IP_PROMPT_TOUT = 10
TEST_ACK_TIMEOUT = 60
TEST_MAX_CIDS = 8
app: IdfApp
serial: IdfSerial
serial: IdfSerial # type: ignore[override]
def __init__(self, *args, **kwargs) -> None: # type: ignore
super().__init__(*args, **kwargs)
self.logger = logging.getLogger()
self.logger: logging.Logger = logging.getLogger()
self.ip_address: Optional[str] = None
self.app_name: Optional[str] = None
self.dut_list: Optional[List[ModbusTestDut]] = None
self.param_fail_count: int = 0
self.param_ok_count: int = 0
self.test_stage = Stages.STACK_DEFAULT
self.dictionary = None
self.test_stage: Stages = Stages.STACK_DEFAULT
self.dictionary: Optional[Dict[Stages, bytes]] = None
self.test_finish: bool = False
self.mb_objects_count: int = (
0 # number of objects (modbus instances in the test application)
@@ -206,9 +245,9 @@ class ModbusTestDut(IdfDut):
# Workaround to get master/slave tag from DUT class
# Checking if app_name contains the field. Ex: modbus_tcp_master
if MASTER_TAG in self.app_name.lower():
obj_tag = MASTER_TAG
obj_tag: str = MASTER_TAG
elif SLAVE_TAG in self.app_name.lower():
obj_tag = SLAVE_TAG
obj_tag: str = SLAVE_TAG
else:
self.logger.error("Could not determine master/slave tag from app_name")
raise RuntimeError from None
@@ -218,7 +257,7 @@ class ModbusTestDut(IdfDut):
def add_object(self, tag: str, id: bytes, timestamp: bytes) -> MbObject:
"""The function add to list master or slave instances in the test"""
obj = MbObject(tag, id, timestamp)
obj: MbObject = MbObject(tag, id, timestamp)
self.mb_objects.append(obj)
self.mb_objects_count += 1
return obj
@@ -228,9 +267,9 @@ class ModbusTestDut(IdfDut):
objects: List[MbObject] = []
self.check_mb_objects_list()
for object in self.mb_objects:
if tag == object.tag:
objects.append(object)
for obj in self.mb_objects:
if tag == obj.tag:
objects.append(obj)
if not objects:
self.logger.error("objects list from tag couldn't be retrieved")
@@ -241,22 +280,33 @@ class ModbusTestDut(IdfDut):
def get_object_by_id(self, id: bytes) -> Optional[MbObject]:
"""The getter retrieves master or slave object by instance address"""
self.check_mb_objects_list()
for object in self.mb_objects:
if id == object.id:
return object
for obj in self.mb_objects:
if id == obj.id:
return obj
self.logger.error(f"couldn't find registered object with id: {id}")
return None
def update_wrong_object_id(self, id: bytes) -> MbObject:
def update_wrong_object_id(self, id: bytes) -> Optional[MbObject]:
"""Scan registered object list checking for a wrongly parsed object ID which is
not 10 characters long. Ex - 0x3ffbf7bc"""
self.check_mb_objects_list()
for object in self.mb_objects:
if len(object.id) != 10:
self.logger.info(f"updating wrong object id: {object.id} to: {id}")
object.id = id
return object
for obj in self.mb_objects:
if len(obj.id) != 10:
self.logger.info(f"Updating wrong object id: {obj.id} to: {id}")
obj.id = id
return obj
def get_params_by_name(self, name: str) -> Optional[List[MbParameter]]:
"""The getter retrieves parameters by name"""
# import sys, pdb; pdb.Pdb(stdout=sys.__stdout__).set_trace()
self.check_mb_objects_list()
params: List[MbParameter] = []
for obj in self.mb_objects:
for param in obj.parameters:
if name in str(param.name):
params.append(param)
return params
def get_params_by_object_id_and_status(
self, instance_address: bytes, status: str
@@ -323,7 +373,7 @@ class ModbusTestDut(IdfDut):
def add_dut_list(self, dut_instance: "ModbusTestDut") -> None:
"""The function keeps track of all DUTs involved in the test"""
if self.dut_list is None:
self.dut_list: List[ModbusTestDut] = []
self.dut_list = []
self.dut_list.append(dut_instance)
return None
@@ -338,9 +388,9 @@ class ModbusTestDut(IdfDut):
def get_avg_response_time_master(self) -> int:
"""The function iterates over master parameters to calculate mean response time"""
master_success_params = self.get_master_params_by_status(PARAM_SUCCESS)
master_success_params: List[MbParameter] = self.get_master_params_by_status(PARAM_SUCCESS)
if master_success_params:
avg_response_time = [
avg_response_time: List[int] = [
param.response_time
for param in master_success_params
if param.response_time is not None
@@ -361,15 +411,17 @@ class ModbusTestDut(IdfDut):
self, transaction_timestamp: bytes, master_address: bytes, cid: bytes
) -> None:
"""The function gets and saves master requests"""
request_response = MbRequestResponse(
int(transaction_timestamp.decode("ascii")), master_address, cid
request_response: MbRequestResponse = MbRequestResponse(
int(transaction_timestamp.decode("ascii")),
master_address,
cid
)
self.mb_request_response.append(request_response)
return None
def add_response_time_to_param(self, param: MbParameter) -> None:
"""The function add response time to parameter entry based on request list"""
last_request_response = self.mb_request_response[-1]
last_request_response: MbRequestResponse = self.mb_request_response[-1]
if (
param.instance_address == last_request_response.master_inst_address
and param.cid == last_request_response.cid
@@ -409,15 +461,24 @@ class ModbusTestDut(IdfDut):
self.logger.info(f"Project name registered: {self.app_name}")
return self.app_name
def dut_send_ip(self, slave_ip: Optional[str]) -> Optional[int]:
def dut_send_ip(self, slave_ip: Optional[str] = None, port: Optional[str] = None) -> Optional[int]:
"""The function sends the slave IP address defined as a parameter to master"""
addr_num: int = 0
self.expect(self.TEST_IP_PROMPT, timeout=self.TEST_EXPECT_STR_TIMEOUT)
try:
self.expect(self.TEST_IP_PROMPT, timeout=self.TEST_IP_PROMPT_TOUT)
except pexpect.TIMEOUT:
# Workaround for unreliable parsing of the master prompt.
# The expect() sometime does not catch it in spite it appears in the log.
# Send the IP address anyway after the timeout.
self.logger.error("Timeout waiting for IP prompt. Try to send address anyway.")
if isinstance(slave_ip, str):
for addr_num in range(0, self.TEST_MAX_CIDS):
message = r"IP{}={}".format(addr_num, slave_ip)
self.logger.info("{} sent to master".format(message))
message: str = r"IP{}={}".format(addr_num, slave_ip)
if isinstance(port, str) or isinstance(port, int):
message += r";{}".format(str(port))
message += r"\r\n"
self.write(message)
self.logger.info("{} sent to master".format(message))
return addr_num
def dut_stats_info(self) -> ModbusDutStats:
@@ -483,7 +544,7 @@ class ModbusTestDut(IdfDut):
label_axis = np.arange(len(names)) * label_space
text_box = (
text_box: str = (
f"Master Average response time:{self.get_avg_response_time_master()} ms"
)
@@ -503,7 +564,7 @@ class ModbusTestDut(IdfDut):
label="Fail",
)
plt.xticks(label_axis, names)
props = dict(boxstyle="round", facecolor="wheat", alpha=alpha)
props: Dict[str, str | float] = dict(boxstyle="round", facecolor="wheat", alpha=alpha)
ax.text(
x_position_text,
y_position_text,
@@ -520,8 +581,29 @@ class ModbusTestDut(IdfDut):
plt.savefig(f"{file_name}.png")
plt.show()
def get_item(self, data: Optional[List[Any]] = None, item: int = 0) -> bytes:
"""
Safely return the requested item from a list as bytes.
On unexpected exceptions returns a bytes sentinel b"N/A".
"""
try:
if not data or len(data) <= item:
# Return sentinel when data missing to keep callers' expectations of bytes
return b"N/A"
val = data[item]
if isinstance(val, bytes):
return val
if isinstance(val, str):
return val.encode("utf-8")
if isinstance(val, (int, float)):
return str(val).encode("utf-8")
# Fallback: stringify and encode any other object
return str(val).encode("utf-8")
except Exception:
return b"N/A"
def expect_any(
self, *expect_items: Tuple[str, Callable], timeout: Optional[int]
self, *expect_items: Tuple[Optional[str], Callable], timeout: Optional[int]
) -> None:
"""
expect_any(*expect_items, timeout=DEFAULT_TIMEOUT)
@@ -537,27 +619,26 @@ class ModbusTestDut(IdfDut):
:keyword timeout: timeout for expect
:return: matched item
"""
def process_expected_item(
item_raw: Tuple[str, Callable[..., Any]],
item_raw: Tuple[Optional[str], Callable[..., Any]],
) -> Dict[str, Any]:
# convert item raw data to standard dict
item = {
"pattern": item_raw[0] if isinstance(item_raw, tuple) else item_raw,
"pattern": item_raw[0] if isinstance(item_raw, tuple) else item_raw or None,
"callback": item_raw[1] if isinstance(item_raw, tuple) else None,
"index": -1,
"ret": None,
}
return item
expect_items_list = [process_expected_item(item) for item in expect_items]
expect_patterns = [
expect_items_list: List[Dict[str, Any]] = [process_expected_item(item) for item in expect_items if isinstance(item, tuple) and item[0]]
expect_patterns: List[Any] = [
item["pattern"] for item in expect_items_list if item["pattern"] is not None
]
match_item = None
match_item: Optional[Dict[str, Any]] = None
if self.pexpect_proc is not None:
match_index = self.pexpect_proc.expect(expect_patterns, timeout)
match_index: int = self.pexpect_proc.expect(expect_patterns, timeout)
if isinstance(match_index, int):
match_item = expect_items_list[match_index] # type: ignore
@@ -579,83 +660,86 @@ class ModbusTestDut(IdfDut):
raise RuntimeError from None
def dut_test_start(
self, dictionary: Dict, timeout_value=TEST_EXPECT_STR_TIMEOUT
self, dictionary: Dict, timeout_value: Optional[int] = TEST_EXPECT_STR_TIMEOUT
) -> None: # type: ignore
"""The method to initialize and handle test stages"""
def handle_get_ip4(data: Any) -> None:
def handle_get_ip4(data: Optional[Any]=None) -> None:
"""Handle get_ip v4"""
# Synch for handling the case where the DUT dont stablish connection from the beginning
# DUT reboot after failing reconnecting
if self.test_stage.value >= Stages.STACK_IPV4.value:
self.logger.info(
"%s: Reboot loop detected on stage [STACK_IPV4], ending test.",
self.app_name,
f"Handle: {self.app_name}: Reboot loop detected on stage [{Stages.STACK_IPV4.name}]: {str(data)}"
)
self.send_message_destroy_other_dut_instances()
self.send_message_destroy_dut()
self.test_finish = True
else:
self.logger.info("%s[STACK_IPV4]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_IPV4
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
def handle_get_ip6(data: Any) -> None:
def handle_get_ip6(data: Optional[Any]=None) -> None:
"""Handle get_ip v6"""
self.logger.info("%s[STACK_IPV6]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_IPV6
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
def handle_init(data: Any) -> None:
def handle_init(data: Optional[Any]=None) -> None:
"""Handle init"""
self.logger.info("%s[STACK_INIT]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_INIT
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
def handle_connect(data: Any) -> None:
def handle_connect(data: Optional[Any]=None) -> None:
"""Handle connect"""
self.logger.info("%s[STACK_CONNECT]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_CONNECT
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
def handle_test_start(data: Any) -> None:
def handle_test_start(data: Optional[Any]=None) -> None:
"""Handle connect"""
self.logger.info("%s[STACK_START]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_START
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
def handle_cid_response_time(data: Any) -> None:
def handle_cid_response_time(data: Optional[Any]=None) -> None:
"""Handle Cid sent request for response time calculation"""
self.logger.info(
"%s[STACK_CID_RESPONSE_TIME]: %s", self.app_name, str(data)
)
self.test_stage = Stages.STACK_CID_RESPONSE_TIME
self.logger.info(
f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}"
)
self.add_request_response(
data[TRANSACTION_TIMESTAMP], data[OBJ_ADDRESS], data[CID]
self.get_item(data, TRANSACTION_TIMESTAMP),
self.get_item(data, OBJ_ADDRESS),
self.get_item(data, CID)
)
def handle_bad_connection(data: Any) -> None:
def handle_bad_connection(data: Optional[Any]=None) -> None:
"""Handle bad connection"""
self.logger.info(
"%s Reached the stage [STACK_BAD_CONNECTION]. Ending the test.",
self.app_name,
)
self.test_stage = Stages.STACK_BAD_CONNECTION
self.logger.info(
f"Handle: {self.app_name}: Reached the stage [{self.test_stage.name}]. Ending the test."
)
self.send_message_destroy_other_dut_instances()
self.send_message_destroy_dut()
self.test_finish = True
def handle_par_ok(data: Any) -> None:
def handle_par_ok(data: Optional[Any]=None) -> None:
"""Handle parameter ok"""
self.logger.info("%s[READ_PAR_OK]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_PAR_OK
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
# Checking if MBobject exist in the object list
object_handle = self.get_object_by_id(data[OBJ_ADDRESS])
object_handle: Optional[MbObject] = self.get_object_by_id(self.get_item(data, OBJ_ADDRESS))
if object_handle is None:
object_handle = self.update_wrong_object_id(data[OBJ_ADDRESS])
object_handle = self.update_wrong_object_id(self.get_item(data, OBJ_ADDRESS))
assert object_handle is not None
last_sucess_parameter = object_handle.add_parameter(
data[PARAM_NAME],
data[OBJ_ADDRESS],
data[TRANSACTION_TIMESTAMP],
last_sucess_parameter: MbParameter = object_handle.add_parameter(
self.get_item(data, PARAM_NAME),
self.get_item(data, OBJ_ADDRESS),
self.get_item(data, TRANSACTION_TIMESTAMP),
object_handle.tag,
PARAM_SUCCESS,
data[CID],
self.get_item(data, CID),
self.get_item(data, PARAM_VAL)
)
if object_handle.is_master():
self.add_response_time_to_param(
@@ -666,51 +750,51 @@ class ModbusTestDut(IdfDut):
self.logger.info(last_sucess_parameter)
self.param_ok_count += 1
self.test_stage = Stages.STACK_PAR_OK
def handle_par_fail(data: Any) -> None:
def handle_par_fail(data: Optional[Any]) -> None:
"""Handle parameter fail"""
self.logger.info("%s[READ_PAR_FAIL]: %s", self.app_name, str(data))
self.test_stage = Stages.STACK_PAR_FAIL
self.logger.info(f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}")
# Checking if MBobject exist in the object list
object_handle = self.get_object_by_id(data[OBJ_ADDRESS])
object_handle: Optional[MbObject] = self.get_object_by_id(self.get_item(data, OBJ_ADDRESS))
if object_handle is None:
object_handle = self.update_wrong_object_id(data[OBJ_ADDRESS])
object_handle = self.update_wrong_object_id(self.get_item(data, OBJ_ADDRESS))
assert object_handle is not None
last_fail_parameter = object_handle.add_parameter(
data[PARAM_NAME],
data[OBJ_ADDRESS],
data[TRANSACTION_TIMESTAMP],
last_fail_parameter: MbParameter = object_handle.add_parameter(
self.get_item(data, PARAM_NAME),
self.get_item(data, OBJ_ADDRESS),
self.get_item(data, TRANSACTION_TIMESTAMP),
object_handle.tag,
PARAM_FAIL,
data[CID],
self.get_item(data, CID),
None
)
self.logger.info(last_fail_parameter)
self.param_fail_count += 1
self.test_stage = Stages.STACK_PAR_FAIL
def handle_obj_create(data: Any) -> None:
def handle_obj_create(data: Optional[Any]) -> None:
"""Handle creation"""
self.logger.info(
"Object creation handled: %s[%s]: %s",
self.app_name,
Stages.STACK_OBJECT_CREATE.name,
str(data),
)
obj_tag = self.validate_object_creation_tag(data[OBJ_TAG].decode("ascii"))
last_add_object = self.add_object(
obj_tag, data[OBJ_ID], data[TRANSACTION_TIMESTAMP]
)
self.test_stage = Stages.STACK_OBJECT_CREATE
self.logger.info(
f"Object creation handled: {self.app_name}[{self.test_stage.name}]: {str(data)}",
)
obj_tag: str = self.validate_object_creation_tag(self.get_item(data, OBJ_TAG).decode("ascii"))
last_add_object: MbObject = self.add_object(
obj_tag,
self.get_item(data, OBJ_ID),
self.get_item(data, TRANSACTION_TIMESTAMP)
)
self.logger.info("New added object: %s", last_add_object)
def handle_destroy(data: Any) -> None:
def handle_destroy(data: Optional[Any]) -> None:
"""Handle destroy"""
self.logger.info(
"%s[%s]: %s", self.app_name, Stages.STACK_DESTROY.name, str(data)
)
self.test_stage = Stages.STACK_DESTROY
self.logger.info(
f"Handle: {self.app_name}[{self.test_stage.name}]: {str(data)}"
)
self.send_message_destroy_other_dut_instances()
self.test_finish = True
@@ -735,10 +819,7 @@ class ModbusTestDut(IdfDut):
)
except pexpect.TIMEOUT:
self.logger.info(
"%s, expect timeout on stage %s (%s seconds)",
self.app_name,
self.test_stage.name,
timeout_value,
f"{self.app_name}, expect timeout on stage {self.test_stage.name} ({timeout_value} seconds)"
)
self.send_message_destroy_other_dut_instances()
self.send_message_destroy_dut()
@@ -746,12 +827,12 @@ class ModbusTestDut(IdfDut):
def dut_check_errors(self) -> None:
"""Verify allowed percentage of errors for the dut"""
allowed_ok_percentage = (
self.param_ok_count / (self.param_ok_count + self.param_fail_count + 1)
) * 100
if self.param_ok_count and (
allowed_ok_percentage > (100 - ALLOWED_PERCENT_OF_FAILS)
):
allowed_ok_percentage : float = 0
if self.param_ok_count or self.param_fail_count:
allowed_ok_percentage = (
self.param_ok_count / (self.param_ok_count + self.param_fail_count) * 100
)
if allowed_ok_percentage > (100 - ALLOWED_PERCENT_OF_FAILS):
self.logger.info(
"%s: ok_count: %d, fail count: %d",
self.app_name,
@@ -775,13 +856,13 @@ class ModbusTestDut(IdfDut):
@pytest.fixture
def case_tester(dut: IdfDut, **kwargs): # type: ignore
def case_tester(dut: IdfDut, **kwargs) -> Generator[CaseTester, Any, None]: # type: ignore
yield CaseTester(dut, **kwargs)
@pytest.fixture(scope="session", autouse=True)
def session_tempdir() -> str:
_tmpdir = os.path.join(
_tmpdir: str = os.path.join(
os.path.dirname(__file__),
"pytest_embedded_log",
datetime.now().strftime("%Y-%m-%d_%H-%M-%S"),
@@ -850,8 +931,10 @@ def build_dir(
check_dirs.append(f"build_{config}")
check_dirs.append("build")
binary_path = ""
for check_dir in check_dirs:
binary_path = os.path.join(app_path, check_dir)
binary_path: str = os.path.join(app_path, check_dir)
if os.path.isdir(binary_path):
logging.info(f"find valid binary path: {binary_path}")
return check_dir