cmvr_head/packages/erpc-1.14.0/test/run_unit_tests.py
2025-12-30 15:44:41 +08:00

909 lines
34 KiB
Python

#! /usr/bin/python
# Copyright (c) 2016 Freescale Semiconductor, Inc.
# Copyright 2016-2024 NXP
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
import argparse
import itertools
import os
import re
import shutil
import sys
from abc import ABC, abstractmethod
from datetime import datetime
from pathlib import Path
from subprocess import run, Popen, TimeoutExpired, PIPE
from time import sleep, time
from typing import Any, Type
from xml.etree import ElementTree
def to_pascal_case(text: str, separator: str = "_") -> str:
return "".join([i.capitalize() for i in text.split("_")])
class TestResult:
def __init__(self):
self.skipped: set[str] = set()
self.passed: set[str] = set()
self.failed: set[str] = set()
self.test_suit_name: str | None = None
@property
def all(self) -> set[str]:
return self.skipped.union(self.passed, self.failed)
@property
def return_code(self) -> int:
return len(self.failed)
def set_test_suit_name(self, test_suit_name: str):
self.test_suit_name = test_suit_name
def add_failed(self, test_case: str):
self.failed.add(test_case)
def add_passed(self, test_case: str):
self.passed.add(test_case)
def add_skipped(self, test_case: str):
self.skipped.add(test_case)
def print_result(self):
padding = 40
print(f"{'':*^{padding}}")
print(f"* {'TEST RESULTS':^{padding - 4}} *")
if self.test_suit_name:
print(f"* {self.test_suit_name:^{padding - 4}} *")
print(f"{'':*^{padding}}")
print(f"\n{len(self.passed)}/{len(self.all)} passed, "
f"{len(self.failed)} failed, "
f"{len(self.skipped)} skipped.")
if len(self.failed) != 0:
print(f"\n{bcolors.RED}Failed tests:")
print("\n".join(f"- {t}" for t in self.failed))
print(bcolors.ENDC)
if len(self.skipped) != 0:
print(f"\n{bcolors.ORANGE}Skipped tests:")
print("\n".join(f"- {t}" for t in self.skipped))
print(bcolors.ENDC)
if len(self.failed) == 0:
print(f"\n{bcolors.GREEN}All tests passed.{bcolors.ENDC}")
class AbstractTests(ABC):
"""
Base class for testing eRPC with different languages.
Define this abstract functions:
- client(): This function should start the eRPC client with test cases
- test(): This function should start the eRPC server
- test_case_server_exist(), test_case_client_exist(): Return if client/server implementation in given language exists
- init_tests(): Init anything that concreate class need to run tests (build, flash, download, ...)
How to use:
- Init client and server with all arguments
- Iter through client's test_cases
- call init_test(test_case, test_suit_name, transport) on client and server
- Check if test case exist for client/server
- call server() on given implementation
- call client() on given implementation
- call wait_client() to get results and wait for client's termination
- call wait_servet() to get results (if server exited correctly)
"""
test_dir_regex = re.compile("test_*")
def __init__(self, result_dir: str | None = None, build_dir: str | None = None, pristine: bool = False,
test_cases: set[str] | None = None, add_test_prefix: bool = False, erpcgen: str | None = None,
**kwargs: dict[str, Any]) -> None:
self.test_dir = Path(os.path.dirname(os.path.abspath(__file__)))
self.erpc_dir = self.test_dir.parent
self.result_dir = Path(result_dir).absolute() if result_dir else self.test_dir.joinpath("results")
self.build_dir = Path(build_dir).absolute() if build_dir else self.erpc_dir.joinpath("build")
if erpcgen:
self.erpcgen_executable = Path(erpcgen).absolute()
elif erpcgen := shutil.which("erpcgen"):
self.erpcgen_executable = Path(erpcgen)
elif self.executable_exists(self.build_dir.joinpath("erpcgen/").joinpath("erpcgen")):
self.erpcgen_executable = self.build_dir.joinpath("erpcgen/").joinpath("erpcgen")
else:
self.erpcgen_executable = None
self.add_test_prefix = add_test_prefix
if not self.result_dir.exists():
self.result_dir.mkdir()
self.pristine = pristine
if test_cases:
self.test_cases = set(test_cases)
else:
self.test_cases = set(self.test_dir.joinpath(p).name for p in os.listdir(self.test_dir) if
self.is_test_dir(self.test_dir.joinpath(p)))
self.server_process: Popen[bytes] | None = None
self.client_process: Popen[bytes] | None = None
self._test_case: str | None = None
self._test_suit_name: str | None = None
self._transport: str | None = None
self.client_extra_parameters: list[str] = []
self.server_extra_parameters: list[str] = []
@property
def test_case(self) -> str:
if self._test_case is None:
raise RuntimeError("Call init_test before client/server.")
return self._test_case
@test_case.setter
def test_case(self, value: str) -> None:
self._test_case = value
@property
def test_suit_name(self) -> str:
if self._test_suit_name is None:
raise RuntimeError("Call init_test before client/server.")
return self._test_suit_name
@test_suit_name.setter
def test_suit_name(self, value: str) -> None:
self._test_suit_name = value
@property
def transport(self) -> str:
if self._transport is None:
raise RuntimeError("Call init_test before client/server.")
return self._transport
@transport.setter
def transport(self, value: str) -> None:
self._transport = value
def init_test(self, test_case: str, test_suit_name: str, transport: str) -> bool:
"""
Initialise next test case values. This function have to be called before each test case.
Can be overridden, bud the function must call super().init_test(test_case, test_suit_name, transport).
@param test_case: Name of the test case
@param test_suit_name: Name of the test suit (tcp_c_c, ...)
@param transport: tcp/serial
@return: Result
"""
self.test_case = test_case
self.test_suit_name = test_suit_name
self.transport = transport
return True
def add_client_parameter(self, parameter: str):
self.client_extra_parameters.append(parameter)
def add_server_parameter(self, parameter: str):
self.server_extra_parameters.append(parameter)
def wait_server(self, timeout: int = 5) -> bool:
"""
Wait for server to termine, otherwise kill the process and return False
@param timeout: Server wait timeout
@return: True if server terminate itself and correctly, False otherwise
"""
if self.server_process is None:
return True
try:
result = self.server_process.wait(timeout) == 0
except TimeoutExpired:
print(f"{bcolors.RED}SERVER TIMEOUT{bcolors.ENDC}")
self.server_process.kill()
result = False
self.server_process = None
return result
def wait_client(self, timeout: int = 5) -> bool:
"""
Wait for client to termine, otherwise kill the process and return False
@param timeout: Client wait timeout
@return: True if client terminate itself and correctly, False otherwise
"""
if self.client_process is None:
return True
try:
result = self.client_process.wait(timeout) == 0
except TimeoutExpired:
print(f"{bcolors.RED}CLIENT TIMEOUT{bcolors.ENDC}")
self.client_process.kill()
self.create_failed_test_suite_xml(
self.test_case,
self.get_result_file()
)
result = False
result_file = self.get_result_file()
if self.add_test_prefix:
self.update_xml_testcases_name(
result_file,
self.test_suit_name
)
self.client_process = None
return result
def get_result_file(self) -> Path:
"""
Get Path to result xml file base on result_dir, test_suit_name and test_case
@return: Path
"""
return self.result_dir.joinpath(f"{self.test_suit_name}_{self.test_case}.xml")
@abstractmethod
def init_tests(self) -> bool:
pass
@abstractmethod
def generate_shim_code(self):
pass
@abstractmethod
def test_case_client_exist(self, test_case: str) -> bool:
"""
Return true if given test_case exists, false otherwise
"""
pass
@abstractmethod
def test_case_server_exist(self, test_case: str) -> bool:
"""
Return true if given test_case exists, false otherwise
"""
pass
@abstractmethod
def client(self) -> None:
"""
Client implementation of given language. It is called for each test case.
Can set self.client_process and then client_wait() to wait for process to termine.
self.client_extra_parameters can be used to modify client command from global scope.
"""
pass
@abstractmethod
def server(self) -> None:
"""
Client implementation of given language. It is called for each test case.
Can set self.server_process and then server_wait() to wait for process to terminate.
self.server_extra_parameters can be used to modify client command from global scope.
"""
pass
@staticmethod
def is_test_dir(directory: Path):
return directory.is_dir and AbstractTests.test_dir_regex.match(directory.name)
@staticmethod
def create_failed_test_suite_xml(test_case: str, result_file: Path):
"""
Create empty failed xml result if test timeout.
@param test_case: Name of the test_case
@param result_file: Output file
"""
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="0" failures="1" disabled="0" errors="0" timestamp="{datetime.now().strftime("%Y-%m-%dT%H:%M:%S")}" time="0.000" name="AllTests">
<testsuite name="{test_case}" tests="0" failures="1" disabled="0" errors="0" time="0.000">
<testcase name="{test_case}" status="run" time="0.015" classname="{test_case}" />
</testsuite>
</testsuites>
"""
with open(result_file, mode="w", encoding="utf8") as fp:
fp.write(xml)
@staticmethod
def update_xml_testcases_name(xml_file: Path, prefix: str = "", suffix: str = ""):
"""
Update xml testcase name with prefix and suffix for better organization.
(Simpler thant trying to change the name in pytest/gtest)
@param xml_file: Path to the xml report
@param prefix: Test case prefix
@param suffix: Test case suffix
"""
tree = ElementTree.parse(xml_file)
root = tree.getroot()
for testsuite in root.findall("testsuite") if root.tag != "testsuite" else [root]:
for testcase in testsuite.findall("testcase"):
testcase_name = testcase.get("name")
testcase.set(
"name",
f"{prefix}{'_' if prefix else ''}{testcase_name}{'_' if suffix else ''}{suffix}"
)
tree.write(xml_file)
@staticmethod
def executable_exists(path: Path) -> bool:
return path.exists() or Path(str(path) + ".exe").exists()
def erpcgen(self, language: str, output_dir: str | Path, idl_file: str | Path, package: str | None = None,
cwd: str | Path | None = None):
erpcgen_command = [
self.erpcgen_executable,
f"-g{language}",
f"-o{output_dir}",
idl_file
]
if package:
erpcgen_command.insert(1, f"-p{package}")
print(f"{bcolors.BLUE}Generating: '{' '.join(str(x) for x in erpcgen_command)}' in {cwd}{bcolors.ENDC}")
erpcgen_proc = Popen(erpcgen_command, cwd=cwd)
res = erpcgen_proc.wait(10)
if res != 0:
raise RuntimeError("Erpcgen execution failed.")
class PythonTests(AbstractTests):
def __init__(self, tcp_port: int, serial_port: str | None = None, **kwargs: dict[str, Any]) -> None:
super().__init__(**kwargs)
self.tcp_port = tcp_port
self.serial_port = serial_port
def init_tests(self) -> bool:
return True
def test_case_client_exist(self, test_case: str) -> bool:
return self.pytest_test_path(test_case).exists()
def test_case_server_exist(self, test_case: str) -> bool:
return self.pytest_test_path(test_case).exists()
def client(self) -> None:
result_file = self.get_result_file()
pytest_command = [
"pytest", str(self.pytest_test_path(self.test_case)),
f"--junitxml={result_file}",
"--client",
*self.client_extra_parameters
]
if self.transport == "tcp":
pytest_command.extend(["--port", str(self.tcp_port)])
elif self.transport == "serial" and self.serial_port:
pytest_command.extend(["--serial", self.serial_port])
else:
raise ValueError
self.client_process = Popen(pytest_command)
def server(self) -> None:
pytest_command = [
"pytest", str(self.pytest_test_path(self.test_case)),
"--server",
*self.server_extra_parameters
]
if self.transport == "tcp":
pytest_command.extend(["--port", str(self.tcp_port)])
elif self.transport == "serial" and self.serial_port:
pytest_command.extend(["--serial", self.serial_port])
else:
raise ValueError
self.server_process = Popen(pytest_command)
sleep(0.5)
def pytest_test_path(self, test_case: str) -> Path:
return self.test_dir.joinpath("python_impl_tests").joinpath(test_case)
def generate_shim_code(self):
for case in self.test_cases:
if self.test_case_client_exist(case) or self.test_case_server_exist(case):
self.erpcgen(
"py",
self.test_dir.joinpath(f"python_impl_tests/{case}/service/"),
self.test_dir.joinpath(f"{case}/{case}.erpc"),
cwd=self.test_dir.joinpath(f"python_impl_tests/")
)
class CTests(AbstractTests):
def __init__(self, c_compiler: str | None = None, cxx_compiler: str | None = None, target: str | None = None,
build_all: bool = False, conf_file: str | None = None, menuconfig: bool = False,
**kwargs: dict[str, Any]) -> None:
super().__init__(**kwargs)
self.menuconfig = menuconfig
self.c_compiler = c_compiler
self.cxx_compiler = cxx_compiler
self.target = target
self.build_all = build_all
self.conf_file = Path(conf_file).absolute() if conf_file else self.test_dir.joinpath("prj.conf")
def init_tests(self) -> bool:
"""
Generate cmake project and build test one by one or all together if --build/-b is selected.
If --menuconfig is enabled, show menu config before build.
@return: set of built tests
"""
if self.pristine and self.build_dir.exists():
print(f"{bcolors.ORANGE}Pristine enabled, removing {self.build_dir}.{bcolors.ENDC}")
shutil.rmtree(self.build_dir)
if not self.build_dir.exists():
self.build_dir.mkdir()
if self.cmake_generate() != 0:
print(f"{bcolors.RED}CMake project generation FAILED.{bcolors.ENDC}")
return False
if self.menuconfig:
if self.cmake_build_target("menuconfig") != 0:
print(f"{bcolors.RED}Building menuconfig for Kconfig FAILED.{bcolors.ENDC}")
return False
if self.build_all:
if self.cmake_build_target("test_all") != 0:
print(f"{bcolors.RED}Tests build FAILED.{bcolors.ENDC}")
return False
else:
for test_case in self.test_cases:
print(f"{bcolors.BLUE}Building: {test_case}{bcolors.ENDC}")
if self.cmake_build_target(test_case) != 0:
print(f"{bcolors.RED}Test'{test_case}' build FAILED.{bcolors.ENDC}")
return True
def test_case_client_exist(self, test_case: str) -> bool:
return self.executable_exists(self.get_test_executable(test_case, "client", self.transport))
def test_case_server_exist(self, test_case: str) -> bool:
return self.executable_exists(self.get_test_executable(test_case, "server", self.transport))
def client(self) -> None:
result_file = self.get_result_file()
client_executable = self.get_test_executable(self.test_case, "client", self.transport)
self.client_process = Popen(
[client_executable, f"--gtest_output=xml:{result_file}", *self.client_extra_parameters])
def server(self) -> None:
server_executable = self.get_test_executable(self.test_case, "server", self.transport)
self.server_process = Popen([server_executable, *self.server_extra_parameters])
sleep(0.5) # Wait for server to start
def get_test_executable(self, test: str, test_type: str, transport: str) -> Path:
"""
Get path to executable of given test, type and transport (work for C++ tests)
@param test: Name of the test
@param test_type: client/server
@param transport: tcp/serial
@return: Path to the executable
"""
executable = self.build_dir.joinpath("test").joinpath(test).joinpath(f"{test}_{test_type}_{transport}")
return executable
def cmake_generate(self) -> int:
"""
Generate cmake project in build dir from erpc as source. Conf file, C and C++ compiler can be defined
@return: Command return code
"""
cmake_command = ["cmake", "-S", str(self.erpc_dir), "-B", str(self.build_dir),
f"-DCMAKE_BUILD_TYPE={self.target}"]
if self.conf_file:
cmake_command.append(f"-DCONF_FILE='{self.conf_file}'")
if self.c_compiler:
cmake_command.append(f"-DCMAKE_C_COMPILER={self.c_compiler}")
if self.c_compiler:
cmake_command.append(f"-DCMAKE_CXX_COMPILER={self.cxx_compiler}")
return run(cmake_command).returncode
def cmake_build_target(self, target: str | None = None) -> int:
"""
Build given target using cmake in build dir
@param target: Target to build or build all
@return: Command return code
"""
if target:
ret = run(["cmake", "--build", str(self.build_dir), "--target", f"{target}"])
else:
ret = run(["cmake", "--build", str(self.build_dir)])
return ret.returncode
def generate_shim_code(self):
pass
class ZephyrTests(AbstractTests):
def __init__(self, zephyr_base: str, serial_port: int, board: str, **kwargs: dict[str, Any]):
super().__init__(**kwargs)
# Zephyr options
self.zephyr_base = Path(zephyr_base)
self.zephyr_erpc = self.zephyr_base.joinpath("modules/lib/erpc/")
self.zephyr_erpc_test = self.zephyr_erpc.joinpath("test/zephyr/uart")
self.serial_port = serial_port
self.board = board
def init_tests(self) -> bool:
return True
def init_test(self, test_case: str, test_suit_name: str, transport: str) -> bool:
super().init_test(test_case, test_suit_name, transport)
cwd = os.getcwd()
os.chdir(self.zephyr_base)
build_dir = Path(f"build/{test_case}").absolute()
if self.pristine and build_dir.exists():
print(f"{bcolors.ORANGE}Pristine enabled, removing Zephyr {str(build_dir)}.{bcolors.ENDC}")
shutil.rmtree(build_dir)
build_command = ["west", "build", "-b", self.board, f"./modules/lib/erpc/test/zephyr/uart/{test_case}", "-d",
f"build/{test_case}"]
flash_command = ["west", "flash", "--build-dir", str(build_dir)]
if run(build_command).returncode != 0:
print(f"{bcolors.RED}Zephyr west build for {test_case} FAILED")
os.chdir(cwd)
return False
if run(flash_command).returncode != 0:
print(f"{bcolors.RED}Zephyr west flash for {test_case} FAILED")
os.chdir(cwd)
return False
os.chdir(cwd)
return True
def test_case_client_exist(self, test_case: str) -> bool:
raise NotImplemented("Zephyr does not support client tests.")
def test_case_server_exist(self, test_case: str) -> bool:
return self.zephyr_erpc_test.joinpath(test_case).exists()
def client(self) -> None:
raise NotImplemented("Zephyr does not support client tests.")
def server(self) -> None:
pass
def generate_shim_code(self):
pass
class JavaTests(AbstractTests):
def __init__(self, maven: str, tcp_port: int, serial_port: str | None = None, **kwargs: dict[str, Any]) -> None:
super().__init__(**kwargs)
self.maven = Path(shutil.which("mvn")) if maven == "mvn" else Path(maven)
self.tcp_port = tcp_port
self.serial_port = serial_port
self.java_test_dir = self.test_dir.joinpath("java_impl_tests/src/test/java/io/github/embeddedrpc/erpc/tests/")
self.java_result_dir = self.test_dir.joinpath("java_impl_tests/target/surefire-reports")
def init_tests(self) -> bool:
return run([self.maven, "generate-test-sources"], cwd=self.test_dir.joinpath("java_impl_tests")).returncode == 0
def test_case_client_exist(self, test_case: str) -> bool:
return self.java_test_dir.joinpath("client").joinpath(f"{to_pascal_case(test_case)}.java").exists()
def test_case_server_exist(self, test_case: str) -> bool:
return self.java_test_dir.joinpath("server").joinpath(f"{to_pascal_case(test_case)}Server.java").exists()
def get_result_file(self) -> Path:
original_file = self.java_result_dir.joinpath(
f"TEST-io.github.embeddedrpc.erpc.tests.client.{to_pascal_case(self.test_case)}.xml")
new_file = self.result_dir.joinpath(f"{self.test_suit_name}_{self.test_case}.xml")
if original_file.exists():
shutil.move(original_file, new_file)
else:
self.create_failed_test_suite_xml(self.test_case, new_file)
return new_file
def client(self) -> None:
mvn_command = [
self.maven, "test",
f"-Dtest={to_pascal_case(self.test_case)}",
"-Dclient",
*self.client_extra_parameters
]
if self.transport == "tcp":
mvn_command.append(f"-Dport={str(self.tcp_port)}")
elif self.transport == "serial" and self.serial_port:
mvn_command.append(f"-Dserial={self.serial_port}")
else:
raise ValueError
self.client_process = Popen(
mvn_command,
cwd=self.test_dir.joinpath('java_impl_tests')
)
def wait_client(self, timeout: int = 5) -> bool:
return super().wait_client(10)
def server(self) -> None:
mvn_command = [
self.maven, "test",
f"-Dtest={to_pascal_case(self.test_case)}Server",
"-Dserver",
*self.server_extra_parameters
]
if self.transport == "tcp":
mvn_command.append(f"-Dport={str(self.tcp_port)}")
elif self.transport == "serial" and self.serial_port:
mvn_command.append(f"-Dserial={self.serial_port}")
else:
raise ValueError
self.server_process = Popen(
mvn_command,
cwd=self.test_dir.joinpath('java_impl_tests'),
stdout=PIPE
)
now = time()
delay = 10
while time() < now + delay:
stdout = self.server_process.stdout.readline()
if stdout.decode().strip() != "":
print(stdout.decode().strip())
if f"Running io.github.embeddedrpc.erpc.tests.server" in stdout.decode():
print("XXX", stdout.decode())
break
sleep(1) # Wait for server to start
def generate_shim_code(self):
for case in self.test_cases:
case_dir = self.test_dir.joinpath(f"java_impl_tests/src/main/java/io/github/embeddedrpc/erpc/tests/{case}/")
case_idl = self.test_dir.joinpath(f"{case}/{case}.erpc")
case_package = f"io.github.embeddedrpc.erpc.tests.{case}.erpc_outputs"
case_cwd = self.test_dir.joinpath(f"java_impl_tests/")
if self.test_case_client_exist(case) or self.test_case_server_exist(case):
if case == "test_struct":
case_idl = self.test_dir.joinpath(f"{case}/test_struct_java.erpc")
if case == "test_arrays":
case_package = f"io.github.embeddedrpc.erpc.tests.{case}"
if case == "test_annotations":
case_package = f"io.github.embeddedrpc.erpc.tests.{case}"
self.erpcgen("java", case_dir, case_idl, case_package, case_cwd)
class bcolors:
# define output text colour class
GREEN = "\033[36m"
BLUE = "\033[38;5;097m"
ORANGE = "\033[38;5;172m"
RED = "\033[31m"
ENDC = "\033[0m"
def run_test(client: AbstractTests, server: AbstractTests, test_suit_name: str, transport: str,
client_extra_parameters: list[str] | None = None,
server_extra_parameters: list[str] | None = None) -> TestResult:
"""
Function to run test combination. Uses implementation of AbstractTests class to start test's client and server
@param client: implementation of AbstractTests
@param server: implementation of AbstractTests
@param test_suit_name: Used to rename result file and test_cases
@param transport: Transport used for testing
@param client_extra_parameters: Extra parameter for executing client tests
@param server_extra_parameters: Extra parameter for executing server tests
@return: TestResult
"""
if not client_extra_parameters:
client_extra_parameters = []
if not server_extra_parameters:
server_extra_parameters = []
client.init_tests()
server.init_tests()
result = TestResult()
# Add extra parameters to client/server
[client.add_client_parameter(x) for x in client_extra_parameters]
[server.add_client_parameter(x) for x in server_extra_parameters]
for test_case in client.test_cases:
# Init each testcase
client.init_test(test_case, test_suit_name, transport)
server.init_test(test_case, test_suit_name, transport)
# Check if test case exist on both sides
if not client.test_case_client_exist(test_case) or not server.test_case_server_exist(test_case):
result.add_skipped(test_case)
continue
# Start client/server
server.server()
client.client()
# Wait for client to finish tests, and server to shut down
client_result = client.wait_client(10)
server_result = server.wait_server(10)
if client_result and server_result:
result.add_passed(test_case)
else:
result.add_failed(test_case)
return result
def main(args: argparse.Namespace) -> int:
"""
Main function that takes parsed arguments and pass them to AbstractTests implementations.
Then call requested combination.
@param args: Parsed CLI arguments
@return: Number of failed tests, 0 if all passed
"""
params = parser.parse_args().__dict__
if type(params["generate_shim"]) is list:
ctest = CTests(**params)
ctest.cmake_generate()
ctest.cmake_build_target("erpcgen")
if "python" in params["generate_shim"] or len(params["generate_shim"]) == 0:
PythonTests(**params).generate_shim_code()
if "java" in params["generate_shim"] or len(params["generate_shim"]) == 0:
JavaTests(**params).generate_shim_code()
tests: list[tuple[str, str, Type[AbstractTests], Type[AbstractTests], str, list[str], list[str]]] = [
("c", "c", CTests, CTests, "tcp", [], []),
("c", "python", CTests, PythonTests, "tcp", ["--gtest_filter=-test_struct.TestSendingByrefMembers"], []),
("c", "java", CTests, JavaTests, "tcp", ["--gtest_filter=-test_struct.TestSendingByrefMembers"], []),
("python", "c", PythonTests, CTests, "tcp", [], []),
("python", "python", PythonTests, PythonTests, "tcp", [], []),
("python", "java", PythonTests, JavaTests, "tcp", [], []),
("java", "c", JavaTests, CTests, "tcp", [], []),
("java", "python", JavaTests, PythonTests, "tcp", [], []),
("java", "java", JavaTests, JavaTests, "tcp", [], []),
("c", "zephyr", CTests, ZephyrTests, "serial", ["--gtest_filter=-test_struct.TestSendingByrefMembers"], []),
("python", "zephyr", PythonTests, ZephyrTests, "serial", [], []),
("java", "zephyr", JavaTests, ZephyrTests, "serial", [], []),
]
results: list[TestResult] = []
return_code = 0
clients = ["c", "python", "java"] if args.client == "all" else [args.client]
servers = ["c", "python", "java"] if args.server == "all" else [args.server]
tests = list(
x[2] for x in # Take given test suit
itertools.product(clients, servers, tests) # From all possible combination
if x[0] == x[2][0] and x[1] == x[2][1] # Filter only mathing
)
for (client, server, client_cls, server_cls, transport, client_args, server_args) in tests:
print(f"{bcolors.RED}RUNNING {transport}_{client}_{server} {bcolors.ENDC}")
result = run_test(
client_cls(**params),
server_cls(**params),
f"{transport}_{client}_{server}",
transport,
client_args,
server_args
)
result.set_test_suit_name(f"{transport}_{client}_{server}")
results.append(result)
return_code += result.return_code
for result in results:
result.print_result()
return return_code
if __name__ == "__main__":
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
prog=Path(__file__).name,
description="""Run eRPC unit tests.
Currently supported options (client - server):
- C - C
- C - Python
- C - Java
- C - Zephyr
- Python - C
- Python - Python
- Python - Java
- Python - Zephyr
- Java - C
- Java - Python
- Java - Java
- Java - Zephyr""")
parser.add_argument("test_cases", nargs="*",
help="List of tests to execute.")
parser.add_argument("-B", "--build-dir",
help="Cmake build dir. If not specified, 'erpc/build' is used.")
parser.add_argument("-b", "--build", action="store_true",
help="Build all before running tests.")
parser.add_argument("-p", "--pristine", action="store_true",
help="Clear the CMake build dir.")
parser.add_argument("--c-compiler",
help="Set CMake default compiler for C (-DCMAKE_C_COMPILER=<compiler>).")
parser.add_argument("--cxx-compiler",
help="Set CMake default compiler for C++ (-DCMAKE_CXX_COMPILER=<compiler>).")
parser.add_argument("-T", "--target", choices=["Debug", "Release"], default="Debug",
help="Set CMake build type (-DCMAKE_BUILD_TYPE=<target>).")
parser.add_argument("--transport", choices=["tcp", "serial"], default="tcp",
help="Set transport layer if there is more options, "
"for example Python-Python only support TCP now.")
parser.add_argument("--conf-file",
help="Set Kconfig config file. Default test/prj.conf")
parser.add_argument("--zephyr-base",
help="Path to Zephyr base directory")
parser.add_argument("-C", "--client", choices=["python", "c", "java", "all"],
help="Set what client should be used. Default C.")
parser.add_argument("-S", "--server", choices=["zephyr", "python", "c", "java", "all"],
help="Set what server should be used. Default C.")
parser.add_argument("--result-dir",
help="Path where xml results from gtest and pytest should be stored. "
"Result files have format '[pytest|gtest]_[test_name]_[transport]_[client]_[server].xml'")
parser.add_argument("--tcp-port", default=12345,
help="TCP port used for pytest. TCP port for C test should be specified in prj.conf")
parser.add_argument("--board", default="mimxrt1060_evkb",
help="Board that would be used for zephyr build.")
parser.add_argument("--serial-port", default="COM4",
help="Serial port that would be used for zephyr build.")
parser.add_argument("--menuconfig", action="store_true",
help="If selected, show menuconfig before building. "
"This allows user to edit tests parameters and choose what to build.")
parser.add_argument("--add-test-prefix", action="store_true",
help="Add prefix to testcase name corresponding to current test setting. "
"TCP Pytohn - C test -> tcp_python_c_test_testName")
parser.add_argument("--maven", default="mvn", help="Path to maven for Java tests. Default 'mvn'.")
parser.add_argument("--erpcgen", help="Path to erpcgen. Default 'erpcgen'.")
parser.add_argument("--generate-shim", nargs="*", help="Generate shim code for Python and Java tests.")
sys.exit(main(parser.parse_args()))