diff --git a/components/esp_eth/test_apps/main/esp_eth_test_apps.c b/components/esp_eth/test_apps/main/esp_eth_test_apps.c index 392abe105866..c3793e20eeec 100644 --- a/components/esp_eth/test_apps/main/esp_eth_test_apps.c +++ b/components/esp_eth/test_apps/main/esp_eth_test_apps.c @@ -94,6 +94,82 @@ TEST_CASE("ethernet io test", "[ethernet]") extra_cleanup(); } +#ifdef CONFIG_TARGET_ETH_PHY_DEVICE_LAN8720 +esp_err_t set_phy_reg_bits(esp_eth_handle_t eth_handle, uint32_t reg_addr, uint32_t bitmask, uint32_t max_attempts) +{ + esp_eth_phy_reg_rw_data_t reg = { + .reg_addr = reg_addr, + .reg_value_p = NULL + }; + uint32_t reg_value, reg_value_rb; + + for (uint32_t i = 0; i < max_attempts; i++) { + reg.reg_value_p = ®_value; + esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®); + if (ret != ESP_OK) { + return ret; + } + reg_value |= bitmask; + ret = esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®); + if (ret != ESP_OK) { + return ret; + } + reg.reg_value_p = ®_value_rb; + ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®); + if (ret != ESP_OK) { + return ret; + } + // Check if the write was successful + if ((reg_value_rb & bitmask) == bitmask) { + return ESP_OK; + } + // Add delay only if not the last attempt + if (i < max_attempts - 1) { + ESP_LOGW(TAG, "Setting PHY register %04"PRIx32" failed, retrying... (attempt %"PRIu32" of %"PRIu32")", reg_addr, i + 1, max_attempts); + vTaskDelay(pdMS_TO_TICKS(10)); + } + } + return ESP_ERR_TIMEOUT; +} + +esp_err_t clear_phy_reg_bits(esp_eth_handle_t eth_handle, uint32_t reg_addr, uint32_t bitmask, uint32_t max_attempts) +{ + esp_eth_phy_reg_rw_data_t reg = { + .reg_addr = reg_addr, + .reg_value_p = NULL + }; + uint32_t reg_value, reg_value_rb; + + for (uint32_t i = 0; i < max_attempts; i++) { + reg.reg_value_p = ®_value; + esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®); + if (ret != ESP_OK) { + return ret; + } + reg_value &= ~bitmask; + ret = esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®); + if (ret != ESP_OK) { + return ret; + } + reg.reg_value_p = ®_value_rb; + ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®); + if (ret != ESP_OK) { + return ret; + } + // Check if the write was successful + if ((reg_value_rb & bitmask) == 0) { + return ESP_OK; + } + // Add delay only if not the last attempt + if (i < max_attempts - 1) { + ESP_LOGW(TAG, "Clearing PHY register %04"PRIx32" failed, retrying... (attempt %"PRIu32" of %"PRIu32")", reg_addr, i + 1, max_attempts); + vTaskDelay(pdMS_TO_TICKS(10)); + } + } + return ESP_ERR_TIMEOUT; +} +#endif // CONFIG_TARGET_ETH_PHY_DEVICE_LAN8720 + // This test expects autonegotiation to be enabled on the other node. TEST_CASE("ethernet io speed/duplex/autonegotiation", "[ethernet]") { @@ -168,17 +244,7 @@ TEST_CASE("ethernet io speed/duplex/autonegotiation", "[ethernet]") // Rationale: When the device is in manual 100BASE-TX or 10BASE-T modes with Auto-MDIX enabled, the PHY does not link to a // link partner that is configured for auto-negotiation. See LAN8720 errata for more details. #ifdef CONFIG_TARGET_ETH_PHY_DEVICE_LAN8720 - esp_eth_phy_reg_rw_data_t reg; - uint32_t reg_val; - reg.reg_addr = 27; - reg.reg_value_p = ®_val; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®)); - reg_val |= 0x8000; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®)); - uint32_t reg_val_act; - reg.reg_value_p = ®_val_act; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®)); - TEST_ASSERT_EQUAL(reg_val, reg_val_act); + TEST_ESP_OK(set_phy_reg_bits(eth_handle, 27, 0x8000, 3)); #endif // start the driver and wait for connection establish @@ -263,13 +329,7 @@ TEST_CASE("ethernet io speed/duplex/autonegotiation", "[ethernet]") // *** LAN8720 deviation *** // Rationale: See above #ifdef CONFIG_TARGET_ETH_PHY_DEVICE_LAN8720 - reg.reg_value_p = ®_val; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®)); - reg_val &= ~0x8000; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®)); - reg.reg_value_p = ®_val_act; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®)); - TEST_ASSERT_EQUAL(reg_val, reg_val_act); + TEST_ESP_OK(clear_phy_reg_bits(eth_handle, 27, 0x8000, 3)); #endif esp_eth_start(eth_handle); @@ -568,17 +628,7 @@ TEST_CASE("ethernet start/stop stress test with IP stack", "[ethernet]") // Rationale: When the device is in manual 100BASE-TX or 10BASE-T modes with Auto-MDIX enabled, the PHY does not link to a // link partner that is configured for auto-negotiation. See LAN8720 errata for more details. #ifdef CONFIG_TARGET_ETH_PHY_DEVICE_LAN8720 - esp_eth_phy_reg_rw_data_t reg; - uint32_t reg_val; - reg.reg_addr = 27; - reg.reg_value_p = ®_val; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®)); - reg_val |= 0x8000; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®)); - uint32_t reg_val_act; - reg.reg_value_p = ®_val_act; - TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®)); - TEST_ASSERT_EQUAL(reg_val, reg_val_act); + TEST_ESP_OK(set_phy_reg_bits(eth_handle, 27, 0x8000, 3)); #endif } for (int i = 0; i < 10; i++) { diff --git a/examples/openthread/ot_ci_function.py b/examples/openthread/ot_ci_function.py index 0c438e9f601c..bc32af980cb7 100644 --- a/examples/openthread/ot_ci_function.py +++ b/examples/openthread/ot_ci_function.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Unlicense OR CC0-1.0 # !/usr/bin/env python3 # this file defines some functions for testing cli and br under pytest framework +import logging import os import re import socket @@ -10,6 +11,7 @@ import time from functools import wraps from typing import Callable +from typing import Optional from typing import Tuple import netifaces @@ -19,19 +21,33 @@ def extract_address( - command: str, pattern: str, default_return: str = '' + command: str, + pattern: str, + default_return: str = '', + retries: int = 3, + delay: int = 2, ) -> Callable[[Callable[[str], str]], Callable[[IdfDut], str]]: def decorator(func: Callable[[str], str]) -> Callable[[IdfDut], str]: @wraps(func) def wrapper(dut: IdfDut) -> str: - clean_buffer(dut) - execute_command(dut, command) - try: - result = dut.expect(pattern, timeout=5)[1].decode() - except Exception as e: - print(f'Error: {e}') - return default_return - return func(result) + # requires Python3.10 + # last_exception: Exception | None = None + last_exception: Optional[Exception] = None + for attempt in range(1, retries + 1): + try: + clean_buffer(dut) + execute_command(dut, command) + result = dut.expect(pattern, timeout=5)[1].decode() + return func(result) + except Exception as e: + logging.exception(f'[{command}] Attempt {attempt}/{retries} failed: {e}') + last_exception = e + if attempt < retries: + time.sleep(delay) + + if last_exception: + logging.exception(f'[{command}] Giving up after {retries} retries.') + return default_return return wrapper @@ -152,7 +168,7 @@ def getDeviceRole(dut: IdfDut) -> str: wait(dut, 1) execute_command(dut, 'state') role = dut.expect(r'\W+(\w+)\W+Done', timeout=5)[1].decode() - print(role) + logging.info(role) return str(role) @@ -173,6 +189,12 @@ def init_thread(dut: IdfDut) -> None: reset_thread(dut) +def stop_thread(dut: IdfDut) -> None: + execute_command(dut, 'thread stop') + dut.expect('disabled', timeout=20) + reset_thread(dut) + + def reset_thread(dut: IdfDut) -> None: execute_command(dut, 'factoryreset') dut.expect('OpenThread attached to netif', timeout=20) @@ -180,28 +202,28 @@ def reset_thread(dut: IdfDut) -> None: clean_buffer(dut) +def hardreset_dut(dut: IdfDut) -> None: + dut.serial.hard_reset() + time.sleep(5) + execute_command(dut, 'factoryreset') + + # get the mleid address of the thread -def get_mleid_addr(dut: IdfDut) -> str: - dut_adress = '' - execute_command(dut, 'ipaddr mleid') - dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode() - return dut_adress +@extract_address('ipaddr mleid', r'\n((?:\w+:){7}\w+)\r') +def get_mleid_addr(addr: str) -> str: + return addr # get the rloc address of the thread -def get_rloc_addr(dut: IdfDut) -> str: - dut_adress = '' - execute_command(dut, 'ipaddr rloc') - dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode() - return dut_adress +@extract_address('ipaddr rloc', r'\n((?:\w+:){7}\w+)\r') +def get_rloc_addr(addr: str) -> str: + return addr # get the linklocal address of the thread -def get_linklocal_addr(dut: IdfDut) -> str: - dut_adress = '' - execute_command(dut, 'ipaddr linklocal') - dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode() - return dut_adress +@extract_address('ipaddr linklocal', r'\n((?:\w+:){7}\w+)\r') +def get_linklocal_addr(addr: str) -> str: + return addr # get the global unicast address of the thread: @@ -311,7 +333,7 @@ def get_host_interface_name() -> str: interface_name = config.get('interface_name') if interface_name: if interface_name == 'eth0': - print( + logging.warning( f"Warning: 'eth0' is not recommended as a valid network interface. " f"Please check and update the 'interface_name' in the configuration file: " f'{config_path}' @@ -319,9 +341,9 @@ def get_host_interface_name() -> str: else: return str(interface_name) else: - print("Warning: Configuration file found but 'interface_name' is not defined.") + logging.warning("Warning: Configuration file found but 'interface_name' is not defined.") except Exception as e: - print(f'Error: Failed to read or parse {config_path}. Details: {e}') + logging.error(f'Error: Failed to read or parse {config_path}. Details: {e}') if 'eth1' in netifaces.interfaces(): return 'eth1' @@ -339,8 +361,8 @@ def check_if_host_receive_ra(br: IdfDut) -> bool: omrprefix = get_omrprefix(br) command = 'ip -6 route | grep ' + str(interface_name) out_str = subprocess.getoutput(command) - print('br omrprefix: ', str(omrprefix)) - print('host route table:\n', str(out_str)) + logging.info(f'br omrprefix: {omrprefix}') + logging.info(f'host route table:\n {out_str}') return str(omrprefix) in str(out_str) @@ -405,7 +427,7 @@ def create_host_udp_server(myudp: udp_parameter) -> None: AF_INET = socket.AF_INET6 else: AF_INET = socket.AF_INET - print('The host start to create udp server!') + logging.info('The host start to create udp server!') if_index = socket.if_nametoindex(interface_name) sock = socket.socket(AF_INET, socket.SOCK_DGRAM) sock.bind((myudp.addr, myudp.port)) @@ -418,13 +440,14 @@ def create_host_udp_server(myudp: udp_parameter) -> None: ) sock.settimeout(myudp.timeout) myudp.init_flag = True - print('The host start to receive message!') + logging.info('The host start to receive message!') myudp.udp_bytes = (sock.recvfrom(1024))[0] - print('The host has received message: ', myudp.udp_bytes) - except socket.error: - print('The host did not receive message!') + udp_str = str(myudp.udp_bytes) + logging.info(f'The host has received message: {udp_str}') + except OSError: + logging.error('The host did not receive message!') finally: - print('Close the socket.') + logging.info('Close the socket.') sock.close() @@ -439,10 +462,10 @@ def host_udp_send_message(udp_target: udp_parameter) -> None: sock.bind(('::', 12350)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, interface_name.encode()) sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 32) - print('Host is sending message') + logging.info('Host is sending message') sock.sendto(udp_target.udp_bytes, (udp_target.addr, udp_target.port)) - except socket.error: - print('Host cannot send message') + except OSError: + logging.error('Host cannot send message') finally: sock.close() @@ -482,13 +505,13 @@ def host_close_service() -> None: command = 'ps auxww | grep avahi-publish-s' out_bytes = subprocess.check_output(command, shell=True, timeout=5) out_str = out_bytes.decode('utf-8') - print('host close service avahi status:\n', out_str) + logging.info(f'host close service avahi status:\n {out_str}') service_info = [line for line in out_str.splitlines() if 'testxxx _testxxx._udp' in line] for line in service_info: - print('Process:', line) + logging.info(f'Process:{line}') pid = line.split()[1] command = 'kill -9 ' + pid - print('kill ', pid) + logging.info(f'kill {pid}') subprocess.call(command, shell=True, timeout=5) time.sleep(1) @@ -521,24 +544,24 @@ def open_host_interface() -> None: def get_domain() -> str: hostname = socket.gethostname() - print('hostname is: ', hostname) + logging.info(f'hostname is: {hostname}') command = 'ps -auxww | grep avahi-daemon | grep running' out_str = subprocess.getoutput(command) - print('avahi status:\n', out_str) + logging.info(f'avahi status:\n {out_str}') role = re.findall(r'\[([\w\W]+)\.local\]', str(out_str))[0] - print('active host is: ', role) + logging.info(f'active host is: {role}') return str(role) def flush_ipv6_addr_by_interface() -> None: interface_name = get_host_interface_name() - print(f'flush ipv6 addr : {interface_name}') + logging.info(f'flush ipv6 addr : {interface_name}') command_show_addr = f'ip -6 addr show dev {interface_name}' command_show_route = f'ip -6 route show dev {interface_name}' addr_before = subprocess.getoutput(command_show_addr) route_before = subprocess.getoutput(command_show_route) - print(f'Before flush, IPv6 addresses: \n{addr_before}') - print(f'Before flush, IPv6 routes: \n{route_before}') + logging.info(f'Before flush, IPv6 addresses: \n{addr_before}') + logging.info(f'Before flush, IPv6 routes: \n{route_before}') subprocess.run(['ip', 'link', 'set', interface_name, 'down']) subprocess.run(['ip', '-6', 'addr', 'flush', 'dev', interface_name]) subprocess.run(['ip', '-6', 'route', 'flush', 'dev', interface_name]) @@ -546,8 +569,8 @@ def flush_ipv6_addr_by_interface() -> None: time.sleep(5) addr_after = subprocess.getoutput(command_show_addr) route_after = subprocess.getoutput(command_show_route) - print(f'After flush, IPv6 addresses: \n{addr_after}') - print(f'After flush, IPv6 routes: \n{route_after}') + logging.info(f'After flush, IPv6 addresses: \n{addr_after}') + logging.info(f'After flush, IPv6 routes: \n{route_after}') class tcp_parameter: @@ -576,28 +599,29 @@ def create_host_tcp_server(mytcp: tcp_parameter) -> None: AF_INET = socket.AF_INET6 else: AF_INET = socket.AF_INET - print('The host start to create a tcp server!') + logging.info('The host start to create a tcp server!') sock = socket.socket(AF_INET, socket.SOCK_STREAM) sock.bind((mytcp.addr, mytcp.port)) sock.listen(5) mytcp.listen_flag = True - print('The tcp server is waiting for connection!') + logging.info('The tcp server is waiting for connection!') sock.settimeout(mytcp.timeout) connfd, addr = sock.accept() - print('The tcp server connected with ', addr) + logging.info(f'The tcp server connected with {addr}') mytcp.recv_flag = True mytcp.tcp_bytes = connfd.recv(1024) - print('The tcp server has received message: ', mytcp.tcp_bytes) + tcp_str = str(mytcp.tcp_bytes) + logging.info(f'The tcp server has received message: {tcp_str}') except socket.error: if mytcp.recv_flag: - print('The tcp server did not receive message!') + logging.error('The tcp server did not receive message!') else: - print('The tcp server fail to connect!') + logging.error('The tcp server fail to connect!') finally: - print('Close the socket.') + logging.info('Close the socket.') sock.close() @@ -617,22 +641,19 @@ def decimal_to_hex(decimal_str: str) -> str: return hex_str -def get_omrprefix(br: IdfDut) -> str: - execute_command(br, 'br omrprefix') - omrprefix = br.expect(r'Local: ((?:\w+:){4}):/\d+\r', timeout=5)[1].decode() - return str(omrprefix) +@extract_address('br omrprefix', r'Local: ((?:\w+:){4}):/\d+\r') +def get_omrprefix(addr: str) -> str: + return addr -def get_onlinkprefix(br: IdfDut) -> str: - execute_command(br, 'br onlinkprefix') - onlinkprefix = br.expect(r'Local: ((?:\w+:){4}):/\d+\r', timeout=5)[1].decode() - return str(onlinkprefix) +@extract_address('br onlinkprefix', r'Local: ((?:\w+:){4}):/\d+\r') +def get_onlinkprefix(addr: str) -> str: + return addr -def get_nat64prefix(br: IdfDut) -> str: - execute_command(br, 'br nat64prefix') - nat64prefix = br.expect(r'Local: ((?:\w+:){6}):/\d+', timeout=5)[1].decode() - return str(nat64prefix) +@extract_address('br nat64prefix', r'Local: ((?:\w+:){6}):/\d+') +def get_nat64prefix(addr: str) -> str: + return addr def execute_command(dut: IdfDut, command: str, prefix: str = 'ot ') -> None: @@ -645,3 +666,17 @@ def get_ouput_string(dut: IdfDut, command: str, wait_time: int) -> str: tmp = dut.expect(pexpect.TIMEOUT, timeout=wait_time) clean_buffer(dut) return str(tmp) + + +def wait_for_host_network(host: str = '8.8.8.8', retries: int = 6, interval: int = 10) -> None: + for attempt in range(1, retries + 1): + try: + subprocess.run(['ping', '-c', '1', '-W', '2', host], check=True) + logging.info(f'Host network reachable on attempt {attempt}') + return + except subprocess.CalledProcessError: + logging.info(f'Ping attempt {attempt} failed, retrying in {interval} seconds...') + if attempt < retries: + time.sleep(interval) + else: + raise RuntimeError(f'Host network is not reachable after {retries} attempts.') diff --git a/examples/openthread/pytest_otbr.py b/examples/openthread/pytest_otbr.py index 36b880179387..670f6bd8eed1 100644 --- a/examples/openthread/pytest_otbr.py +++ b/examples/openthread/pytest_otbr.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Unlicense OR CC0-1.0 # !/usr/bin/env python3 import copy +import logging import os.path import random import re @@ -76,7 +77,7 @@ @pytest.fixture(scope='module', name='Init_avahi') def fixture_Init_avahi() -> bool: - print('Init Avahi') + logging.info('Init Avahi') ocf.start_avahi() time.sleep(10) return True @@ -84,7 +85,7 @@ def fixture_Init_avahi() -> bool: @pytest.fixture(name='Init_interface') def fixture_Init_interface() -> bool: - print('Init interface') + logging.info('Init interface') ocf.flush_ipv6_addr_by_interface() # The sleep time is set based on experience; reducing it might cause the host to be unready. time.sleep(30) @@ -109,7 +110,7 @@ def fixture_Init_interface() -> bool: # Case 1: Thread network formation and attaching @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -136,7 +137,7 @@ def fixture_Init_interface() -> bool: ], indirect=True, ) -def test_thread_connect(dut:Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_thread_connect(dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli_h2 = dut[1] dut[0].serial.stop_redirect_thread() @@ -164,9 +165,9 @@ def test_thread_connect(dut:Tuple[IdfDut, IdfDut, IdfDut]) -> None: rx_nums = ocf.ot_ping(br, cli_mleid_addr, count=5)[1] assert rx_nums == 5 finally: - ocf.execute_command(br, 'factoryreset') for cli in cli_list: - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) @@ -193,7 +194,7 @@ def formBasicWiFiThreadNetwork(br:IdfDut, cli:IdfDut) -> None: # Case 2: Bidirectional IPv6 connectivity @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -210,7 +211,7 @@ def formBasicWiFiThreadNetwork(br:IdfDut, cli:IdfDut) -> None: ], indirect=True, ) -def test_Bidirectional_IPv6_connectivity(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_Bidirectional_IPv6_connectivity(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -220,10 +221,10 @@ def test_Bidirectional_IPv6_connectivity(Init_interface:bool, dut: Tuple[IdfDut, try: assert ocf.is_joined_wifi_network(br) cli_global_unicast_addr = ocf.get_global_unicast_addr(cli, br) - print('cli_global_unicast_addr', cli_global_unicast_addr) + logging.info(f'cli_global_unicast_addr {cli_global_unicast_addr}') command = 'ping ' + str(cli_global_unicast_addr) + ' -c 10' out_str = subprocess.getoutput(command) - print('ping result:\n', str(out_str)) + logging.info(f'ping result:\n{out_str}') role = re.findall(r' (\d+)%', str(out_str))[0] assert role != '100' interface_name = ocf.get_host_interface_name() @@ -231,22 +232,24 @@ def test_Bidirectional_IPv6_connectivity(Init_interface:bool, dut: Tuple[IdfDut, out_bytes = subprocess.check_output(command, shell=True, timeout=5) out_str = out_bytes.decode('utf-8') onlinkprefix = ocf.get_onlinkprefix(br) - host_global_unicast_addr = re.findall(r'\W+(%s(?:\w+:){3}\w+)\W+' % onlinkprefix, str(out_str)) + pattern = rf'\W+({onlinkprefix}(?:\w+:){{3}}\w+)\W+' + host_global_unicast_addr = re.findall(pattern, out_str) rx_nums = 0 for ip_addr in host_global_unicast_addr: - txrx_nums = ocf.ot_ping(cli, str(ip_addr), count=5) + txrx_nums = ocf.ot_ping(cli, str(ip_addr), count=10) rx_nums = rx_nums + int(txrx_nums[1]) + logging.debug(f'rx_nums: {rx_nums}') assert rx_nums != 0 finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 3: Multicast forwarding from Wi-Fi to Thread network @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -263,7 +266,7 @@ def test_Bidirectional_IPv6_connectivity(Init_interface:bool, dut: Tuple[IdfDut, ], indirect=True, ) -def test_multicast_forwarding_A(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_multicast_forwarding_A(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -278,7 +281,7 @@ def test_multicast_forwarding_A(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, interface_name = ocf.get_host_interface_name() command = 'ping -I ' + str(interface_name) + ' -t 64 ff04::125 -c 10' out_str = subprocess.getoutput(command) - print('ping result:\n', str(out_str)) + logging.info(f'ping result:\n{out_str}') role = re.findall(r' (\d+)%', str(out_str))[0] assert role != '100' ocf.execute_command(cli, 'udp open') @@ -292,15 +295,15 @@ def test_multicast_forwarding_A(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, ocf.execute_command(cli, 'udp close') cli.expect('Done', timeout=5) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 4: Multicast forwarding from Thread to Wi-Fi network @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -317,7 +320,7 @@ def test_multicast_forwarding_A(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, ], indirect=True, ) -def test_multicast_forwarding_B(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_multicast_forwarding_B(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -346,8 +349,8 @@ def test_multicast_forwarding_B(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, while udp_mission.is_alive(): time.sleep(1) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) assert b'hello' in myudp.udp_bytes @@ -355,7 +358,7 @@ def test_multicast_forwarding_B(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, # Case 5: discover dervice published by Thread device @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -372,7 +375,9 @@ def test_multicast_forwarding_B(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, ], indirect=True, ) -def test_service_discovery_of_Thread_device(Init_interface:bool, Init_avahi:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_service_discovery_of_Thread_device( + Init_interface: bool, Init_avahi: bool, dut: Tuple[IdfDut, IdfDut, IdfDut] +) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -384,14 +389,14 @@ def test_service_discovery_of_Thread_device(Init_interface:bool, Init_avahi:bool assert ocf.is_joined_wifi_network(br) command = 'avahi-browse -rt _testyyy._udp' out_str = subprocess.getoutput(command) - print('avahi-browse:\n', str(out_str)) + logging.info(f'avahi-browse:\n{out_str}') assert 'myTest' not in str(out_str) hostname = 'myTest' command = 'srp client host name ' + hostname ocf.execute_command(cli, command) cli.expect('Done', timeout=5) cli_global_unicast_addr = ocf.get_global_unicast_addr(cli, br) - print('cli_global_unicast_addr', cli_global_unicast_addr) + logging.info(f'cli_global_unicast_addr {cli_global_unicast_addr}') command = 'srp client host address ' + str(cli_global_unicast_addr) ocf.execute_command(cli, command) cli.expect('Done', timeout=5) @@ -404,18 +409,18 @@ def test_service_discovery_of_Thread_device(Init_interface:bool, Init_avahi:bool ocf.wait(cli, 3) command = 'avahi-browse -rt _testyyy._udp' out_str = subprocess.getoutput(command) - print('avahi-browse:\n', str(out_str)) + logging.info(f'avahi-browse:\n {out_str}') assert 'myTest' in str(out_str) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 6: discover dervice published by Wi-Fi device @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -432,7 +437,9 @@ def test_service_discovery_of_Thread_device(Init_interface:bool, Init_avahi:bool ], indirect=True, ) -def test_service_discovery_of_WiFi_device(Init_interface:bool, Init_avahi:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_service_discovery_of_WiFi_device( + Init_interface: bool, Init_avahi: bool, dut: Tuple[IdfDut, IdfDut, IdfDut] +) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -448,7 +455,7 @@ def test_service_discovery_of_WiFi_device(Init_interface:bool, Init_avahi:bool, cli.expect('Done', timeout=5) ocf.wait(cli, 1) domain_name = ocf.get_domain() - print('domain name is: ', domain_name) + logging.info(f'domain name is: {domain_name}') command = 'dns resolve ' + domain_name + '.default.service.arpa.' ocf.execute_command(cli, command) @@ -476,15 +483,15 @@ def test_service_discovery_of_WiFi_device(Init_interface:bool, Init_avahi:bool, finally: ocf.host_close_service() sp.terminate() - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 7: ICMP communication via NAT64 @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -501,7 +508,7 @@ def test_service_discovery_of_WiFi_device(Init_interface:bool, Init_avahi:bool, ], indirect=True, ) -def test_ICMP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_ICMP_NAT64(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -511,19 +518,19 @@ def test_ICMP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> try: assert ocf.is_joined_wifi_network(br) host_ipv4_address = ocf.get_host_ipv4_address() - print('host_ipv4_address: ', host_ipv4_address) + logging.info(f'host_ipv4_address: {host_ipv4_address}') rx_nums = ocf.ot_ping(cli, str(host_ipv4_address), count=5)[1] assert rx_nums != 0 finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 8: UDP communication via NAT64 @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -540,7 +547,7 @@ def test_ICMP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> ], indirect=True, ) -def test_UDP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_UDP_NAT64(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -555,7 +562,7 @@ def test_UDP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N cli.expect('Done', timeout=5) ocf.wait(cli, 3) host_ipv4_address = ocf.get_host_ipv4_address() - print('host_ipv4_address: ', host_ipv4_address) + logging.info(f'host_ipv4_address: {host_ipv4_address}') myudp = ocf.udp_parameter('INET4', host_ipv4_address, 5090, '', False, 15.0, b'') udp_mission = threading.Thread(target=ocf.create_host_udp_server, args=(myudp, )) udp_mission.start() @@ -571,8 +578,8 @@ def test_UDP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N while udp_mission.is_alive(): time.sleep(1) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) assert b'hello' in myudp.udp_bytes @@ -580,7 +587,7 @@ def test_UDP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N # Case 9: TCP communication via NAT64 @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -597,7 +604,7 @@ def test_UDP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N ], indirect=True, ) -def test_TCP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_TCP_NAT64(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -613,7 +620,7 @@ def test_TCP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N ocf.wait(cli, 3) host_ipv4_address = ocf.get_host_ipv4_address() connect_address = ocf.get_ipv6_from_ipv4(host_ipv4_address, br) - print('connect_address is: ', connect_address) + logging.info(f'connect_address is: {connect_address}') mytcp = ocf.tcp_parameter('INET4', host_ipv4_address, 12345, False, False, 15.0, b'') tcp_mission = threading.Thread(target=ocf.create_host_tcp_server, args=(mytcp, )) tcp_mission.start() @@ -634,8 +641,8 @@ def test_TCP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N while tcp_mission.is_alive(): time.sleep(1) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) assert b'hello' in mytcp.tcp_bytes @@ -644,6 +651,7 @@ def test_TCP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N @pytest.mark.esp32h2 @pytest.mark.esp32c6 @pytest.mark.openthread_sleep +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -671,6 +679,7 @@ def test_TCP_NAT64(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N def test_ot_sleepy_device(dut: Tuple[IdfDut, IdfDut]) -> None: leader = dut[0] sleepy_device = dut[1] + ocf.hardreset_dut(sleepy_device) fail_info = re.compile(r'Core\W*?\d\W*?register dump') try: ocf.init_thread(leader) @@ -697,13 +706,14 @@ def test_ot_sleepy_device(dut: Tuple[IdfDut, IdfDut]) -> None: assert not bool(fail_info.search(str(output))) finally: ocf.execute_command(leader, 'factoryreset') + ocf.hardreset_dut(sleepy_device) time.sleep(3) # Case 11: Basic startup Test of BR @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -738,14 +748,14 @@ def test_basic_startup(dut: Tuple[IdfDut, IdfDut]) -> None: br.expect('Done', timeout=5) assert ocf.wait_for_join(br, 'leader') finally: - ocf.execute_command(br, 'factoryreset') + ocf.stop_thread(br) time.sleep(3) # Case 12: Curl a website via DNS and NAT64 @pytest.mark.supported_targets @pytest.mark.openthread_bbr -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -762,7 +772,7 @@ def test_basic_startup(dut: Tuple[IdfDut, IdfDut]) -> None: ], indirect=True, ) -def test_NAT64_DNS(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_NAT64_DNS(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -770,24 +780,25 @@ def test_NAT64_DNS(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N formBasicWiFiThreadNetwork(br, cli) try: + ocf.wait_for_host_network() ocf.execute_command(br, 'bbr') br.expect('server16', timeout=5) ocf.execute_command(cli, 'dns64server 8.8.8.8') cli.expect('Done', timeout=5) command = 'curl http://www.espressif.com' message = ocf.get_ouput_string(cli, command, 10) - assert '' in str(message) + assert 'html' in str(message) assert '301 Moved Permanently' in str(message) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 13: Meshcop discovery of Border Router @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -803,7 +814,7 @@ def test_NAT64_DNS(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> N ], indirect=True, ) -def test_br_meshcop(Init_interface:bool, Init_avahi:bool, dut: Tuple[IdfDut, IdfDut]) -> None: +def test_br_meshcop(Init_interface: bool, Init_avahi: bool, dut: Tuple[IdfDut, IdfDut]) -> None: br = dut[1] assert Init_interface assert Init_avahi @@ -829,9 +840,9 @@ def test_br_meshcop(Init_interface:bool, Init_avahi:bool, dut: Tuple[IdfDut, Idf except subprocess.CalledProcessError as e: output_bytes = e.stdout finally: - print('out_bytes: ', output_bytes) + logging.info(f'out_bytes: {output_bytes!r}') output_str = str(output_bytes) - print('out_str: ', output_str) + logging.info(f'out_str: {output_str}') assert 'hostname = [esp-ot-br.local]' in str(output_str) assert ('address = [' + ipv4_address + ']') in str(output_str) @@ -842,14 +853,14 @@ def test_br_meshcop(Init_interface:bool, Init_avahi:bool, dut: Tuple[IdfDut, Idf assert 'vn=OpenThread' in str(output_str) assert 'rv=1' in str(output_str) finally: - ocf.execute_command(br, 'factoryreset') + ocf.stop_thread(br) time.sleep(3) # Case 14: Curl a website over HTTPS via DNS and NAT64 @pytest.mark.supported_targets @pytest.mark.openthread_bbr -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -866,7 +877,7 @@ def test_br_meshcop(Init_interface:bool, Init_avahi:bool, dut: Tuple[IdfDut, Idf ], indirect=True, ) -def test_https_NAT64_DNS(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: +def test_https_NAT64_DNS(Init_interface: bool, dut: Tuple[IdfDut, IdfDut, IdfDut]) -> None: br = dut[2] cli = dut[1] assert Init_interface @@ -874,22 +885,23 @@ def test_https_NAT64_DNS(Init_interface:bool, dut: Tuple[IdfDut, IdfDut, IdfDut] formBasicWiFiThreadNetwork(br, cli) try: + ocf.wait_for_host_network() ocf.execute_command(cli, 'dns64server 8.8.8.8') cli.expect('Done', timeout=5) command = 'curl https://www.example.com/' message = ocf.get_ouput_string(cli, command, 20) - assert '' in str(message) - assert 'This domain is for use in illustrative examples in documents' in str(message) + assert 'html' in str(message) + assert 'This domain is for use in' in str(message) finally: - ocf.execute_command(br, 'factoryreset') - ocf.execute_command(cli, 'factoryreset') + ocf.stop_thread(cli) + ocf.stop_thread(br) time.sleep(3) # Case 15: Thread network formation and attaching with TREL @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -935,16 +947,16 @@ def test_trel_connect(dut: Tuple[IdfDut, IdfDut]) -> None: rx_nums = ocf.ot_ping(trel_s3, trel_mleid_addr, count=10)[1] assert rx_nums > 5 finally: - ocf.execute_command(trel_s3, 'factoryreset') for trel in trel_list: - ocf.execute_command(trel, 'factoryreset') + ocf.stop_thread(trel) + ocf.stop_thread(trel_s3) time.sleep(3) # Case 16: Thread network BR lib check @pytest.mark.supported_targets @pytest.mark.openthread_br -@pytest.mark.flaky(reruns=1, reruns_delay=1) +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -974,6 +986,7 @@ def test_br_lib_check(dut: Tuple[IdfDut, IdfDut]) -> None: # Case 17: SSED test @pytest.mark.openthread_sleep +@pytest.mark.flaky(reruns=1, reruns_delay=5) @pytest.mark.parametrize( 'config, count, app_path, target, port', [ @@ -1002,6 +1015,7 @@ def test_ot_ssed_device(dut: Tuple[IdfDut, IdfDut]) -> None: leader = dut[0] ssed_device = dut[1] try: + ocf.hardreset_dut(ssed_device) # CI device must have external XTAL to run SSED case, we will check this here first ssed_device.expect('32k XTAL in use', timeout=10) ocf.init_thread(leader) @@ -1041,4 +1055,5 @@ def test_ot_ssed_device(dut: Tuple[IdfDut, IdfDut]) -> None: ocf.ping_and_check(dut=leader, target=ssed_address, tx_total=10, timeout=6) finally: ocf.execute_command(leader, 'factoryreset') + ocf.hardreset_dut(ssed_device) time.sleep(3)