Skip to content

Commit 621ab81

Browse files
author
Jiang Jiang Jian
committed
Merge branch 'bugfix/fix_thread_ci_testcases_not_stable_v5.5' into 'release/v5.5'
Bugfix/fix thread ci testcases not stable v5.5 See merge request espressif/esp-idf!43150
2 parents f702d40 + 20710cc commit 621ab81

File tree

2 files changed

+177
-130
lines changed

2 files changed

+177
-130
lines changed

examples/openthread/ot_ci_function.py

Lines changed: 106 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
# SPDX-License-Identifier: Unlicense OR CC0-1.0
33
# !/usr/bin/env python3
44
# this file defines some functions for testing cli and br under pytest framework
5+
import logging
56
import os
67
import re
78
import socket
89
import struct
910
import subprocess
1011
import time
11-
from collections.abc import Callable
1212
from functools import wraps
13+
from typing import Callable
14+
from typing import Optional
15+
from typing import Tuple
1316

1417
import netifaces
1518
import pexpect
@@ -18,19 +21,33 @@
1821

1922

2023
def extract_address(
21-
command: str, pattern: str, default_return: str = ''
24+
command: str,
25+
pattern: str,
26+
default_return: str = '',
27+
retries: int = 3,
28+
delay: int = 2,
2229
) -> Callable[[Callable[[str], str]], Callable[[IdfDut], str]]:
2330
def decorator(func: Callable[[str], str]) -> Callable[[IdfDut], str]:
2431
@wraps(func)
2532
def wrapper(dut: IdfDut) -> str:
26-
clean_buffer(dut)
27-
execute_command(dut, command)
28-
try:
29-
result = dut.expect(pattern, timeout=5)[1].decode()
30-
except Exception as e:
31-
print(f'Error: {e}')
32-
return default_return
33-
return func(result)
33+
# requires Python3.10
34+
# last_exception: Exception | None = None
35+
last_exception: Optional[Exception] = None
36+
for attempt in range(1, retries + 1):
37+
try:
38+
clean_buffer(dut)
39+
execute_command(dut, command)
40+
result = dut.expect(pattern, timeout=5)[1].decode()
41+
return func(result)
42+
except Exception as e:
43+
logging.exception(f'[{command}] Attempt {attempt}/{retries} failed: {e}')
44+
last_exception = e
45+
if attempt < retries:
46+
time.sleep(delay)
47+
48+
if last_exception:
49+
logging.exception(f'[{command}] Giving up after {retries} retries.')
50+
return default_return
3451

3552
return wrapper
3653

@@ -132,7 +149,7 @@ def wait_for_join(dut: IdfDut, role: str) -> bool:
132149
return False
133150

134151

135-
def joinWiFiNetwork(dut: IdfDut, wifi: wifi_parameter) -> tuple[str, int]:
152+
def joinWiFiNetwork(dut: IdfDut, wifi: wifi_parameter) -> Tuple[str, int]:
136153
clean_buffer(dut)
137154
ip_address = ''
138155
for order in range(1, wifi.retry_times):
@@ -151,7 +168,7 @@ def getDeviceRole(dut: IdfDut) -> str:
151168
wait(dut, 1)
152169
execute_command(dut, 'state')
153170
role = dut.expect(r'\W+(\w+)\W+Done', timeout=5)[1].decode()
154-
print(role)
171+
logging.info(role)
155172
return str(role)
156173

157174

@@ -172,35 +189,41 @@ def init_thread(dut: IdfDut) -> None:
172189
reset_thread(dut)
173190

174191

192+
def stop_thread(dut: IdfDut) -> None:
193+
execute_command(dut, 'thread stop')
194+
dut.expect('disabled', timeout=20)
195+
reset_thread(dut)
196+
197+
175198
def reset_thread(dut: IdfDut) -> None:
176199
execute_command(dut, 'factoryreset')
177200
dut.expect('OpenThread attached to netif', timeout=20)
178201
wait(dut, 3)
179202
clean_buffer(dut)
180203

181204

205+
def hardreset_dut(dut: IdfDut) -> None:
206+
dut.serial.hard_reset()
207+
time.sleep(5)
208+
execute_command(dut, 'factoryreset')
209+
210+
182211
# get the mleid address of the thread
183-
def get_mleid_addr(dut: IdfDut) -> str:
184-
dut_adress = ''
185-
execute_command(dut, 'ipaddr mleid')
186-
dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode()
187-
return str(dut_adress)
212+
@extract_address('ipaddr mleid', r'\n((?:\w+:){7}\w+)\r')
213+
def get_mleid_addr(addr: str) -> str:
214+
return addr
188215

189216

190217
# get the rloc address of the thread
191-
def get_rloc_addr(dut: IdfDut) -> str:
192-
dut_adress = ''
193-
execute_command(dut, 'ipaddr rloc')
194-
dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode()
195-
return str(dut_adress)
218+
@extract_address('ipaddr rloc', r'\n((?:\w+:){7}\w+)\r')
219+
def get_rloc_addr(addr: str) -> str:
220+
return addr
196221

197222

198223
# get the linklocal address of the thread
199-
def get_linklocal_addr(dut: IdfDut) -> str:
200-
dut_adress = ''
201-
execute_command(dut, 'ipaddr linklocal')
202-
dut_adress = dut.expect(r'\n((?:\w+:){7}\w+)\r', timeout=5)[1].decode()
203-
return str(dut_adress)
224+
@extract_address('ipaddr linklocal', r'\n((?:\w+:){7}\w+)\r')
225+
def get_linklocal_addr(addr: str) -> str:
226+
return addr
204227

205228

206229
# get the global unicast address of the thread:
@@ -221,7 +244,7 @@ def get_rloc16_addr(rloc16: str) -> str:
221244
# ping of thread
222245
def ot_ping(
223246
dut: IdfDut, target: str, timeout: int = 5, count: int = 1, size: int = 56, interval: int = 1, hoplimit: int = 64
224-
) -> tuple[int, int]:
247+
) -> Tuple[int, int]:
225248
command = f'ping {str(target)} {size} {count} {interval} {hoplimit} {str(timeout)}'
226249
execute_command(dut, command)
227250
transmitted = dut.expect(r'(\d+) packets transmitted', timeout=60)[1].decode()
@@ -310,17 +333,17 @@ def get_host_interface_name() -> str:
310333
interface_name = config.get('interface_name')
311334
if interface_name:
312335
if interface_name == 'eth0':
313-
print(
336+
logging.warning(
314337
f"Warning: 'eth0' is not recommended as a valid network interface. "
315338
f"Please check and update the 'interface_name' in the configuration file: "
316339
f'{config_path}'
317340
)
318341
else:
319342
return str(interface_name)
320343
else:
321-
print("Warning: Configuration file found but 'interface_name' is not defined.")
344+
logging.warning("Warning: Configuration file found but 'interface_name' is not defined.")
322345
except Exception as e:
323-
print(f'Error: Failed to read or parse {config_path}. Details: {e}')
346+
logging.error(f'Error: Failed to read or parse {config_path}. Details: {e}')
324347
if 'eth1' in netifaces.interfaces():
325348
return 'eth1'
326349

@@ -338,8 +361,8 @@ def check_if_host_receive_ra(br: IdfDut) -> bool:
338361
omrprefix = get_omrprefix(br)
339362
command = 'ip -6 route | grep ' + str(interface_name)
340363
out_str = subprocess.getoutput(command)
341-
print('br omrprefix: ', str(omrprefix))
342-
print('host route table:\n', str(out_str))
364+
logging.info(f'br omrprefix: {omrprefix}')
365+
logging.info(f'host route table:\n {out_str}')
343366
return str(omrprefix) in str(out_str)
344367

345368

@@ -404,7 +427,7 @@ def create_host_udp_server(myudp: udp_parameter) -> None:
404427
AF_INET = socket.AF_INET6
405428
else:
406429
AF_INET = socket.AF_INET
407-
print('The host start to create udp server!')
430+
logging.info('The host start to create udp server!')
408431
if_index = socket.if_nametoindex(interface_name)
409432
sock = socket.socket(AF_INET, socket.SOCK_DGRAM)
410433
sock.bind((myudp.addr, myudp.port))
@@ -417,13 +440,14 @@ def create_host_udp_server(myudp: udp_parameter) -> None:
417440
)
418441
sock.settimeout(myudp.timeout)
419442
myudp.init_flag = True
420-
print('The host start to receive message!')
443+
logging.info('The host start to receive message!')
421444
myudp.udp_bytes = (sock.recvfrom(1024))[0]
422-
print('The host has received message: ', myudp.udp_bytes)
445+
udp_str = str(myudp.udp_bytes)
446+
logging.info(f'The host has received message: {udp_str}')
423447
except OSError:
424-
print('The host did not receive message!')
448+
logging.error('The host did not receive message!')
425449
finally:
426-
print('Close the socket.')
450+
logging.info('Close the socket.')
427451
sock.close()
428452

429453

@@ -438,10 +462,10 @@ def host_udp_send_message(udp_target: udp_parameter) -> None:
438462
sock.bind(('::', 12350))
439463
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, interface_name.encode())
440464
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 32)
441-
print('Host is sending message')
465+
logging.info('Host is sending message')
442466
sock.sendto(udp_target.udp_bytes, (udp_target.addr, udp_target.port))
443467
except OSError:
444-
print('Host cannot send message')
468+
logging.error('Host cannot send message')
445469
finally:
446470
sock.close()
447471

@@ -481,13 +505,13 @@ def host_close_service() -> None:
481505
command = 'ps auxww | grep avahi-publish-s'
482506
out_bytes = subprocess.check_output(command, shell=True, timeout=5)
483507
out_str = out_bytes.decode('utf-8')
484-
print('host close service avahi status:\n', out_str)
508+
logging.info(f'host close service avahi status:\n {out_str}')
485509
service_info = [line for line in out_str.splitlines() if 'testxxx _testxxx._udp' in line]
486510
for line in service_info:
487-
print('Process:', line)
511+
logging.info(f'Process:{line}')
488512
pid = line.split()[1]
489513
command = 'kill -9 ' + pid
490-
print('kill ', pid)
514+
logging.info(f'kill {pid}')
491515
subprocess.call(command, shell=True, timeout=5)
492516
time.sleep(1)
493517

@@ -520,33 +544,33 @@ def open_host_interface() -> None:
520544

521545
def get_domain() -> str:
522546
hostname = socket.gethostname()
523-
print('hostname is: ', hostname)
547+
logging.info(f'hostname is: {hostname}')
524548
command = 'ps -auxww | grep avahi-daemon | grep running'
525549
out_str = subprocess.getoutput(command)
526-
print('avahi status:\n', out_str)
550+
logging.info(f'avahi status:\n {out_str}')
527551
role = re.findall(r'\[([\w\W]+)\.local\]', str(out_str))[0]
528-
print('active host is: ', role)
552+
logging.info(f'active host is: {role}')
529553
return str(role)
530554

531555

532556
def flush_ipv6_addr_by_interface() -> None:
533557
interface_name = get_host_interface_name()
534-
print(f'flush ipv6 addr : {interface_name}')
558+
logging.info(f'flush ipv6 addr : {interface_name}')
535559
command_show_addr = f'ip -6 addr show dev {interface_name}'
536560
command_show_route = f'ip -6 route show dev {interface_name}'
537561
addr_before = subprocess.getoutput(command_show_addr)
538562
route_before = subprocess.getoutput(command_show_route)
539-
print(f'Before flush, IPv6 addresses: \n{addr_before}')
540-
print(f'Before flush, IPv6 routes: \n{route_before}')
563+
logging.info(f'Before flush, IPv6 addresses: \n{addr_before}')
564+
logging.info(f'Before flush, IPv6 routes: \n{route_before}')
541565
subprocess.run(['ip', 'link', 'set', interface_name, 'down'])
542566
subprocess.run(['ip', '-6', 'addr', 'flush', 'dev', interface_name])
543567
subprocess.run(['ip', '-6', 'route', 'flush', 'dev', interface_name])
544568
subprocess.run(['ip', 'link', 'set', interface_name, 'up'])
545569
time.sleep(5)
546570
addr_after = subprocess.getoutput(command_show_addr)
547571
route_after = subprocess.getoutput(command_show_route)
548-
print(f'After flush, IPv6 addresses: \n{addr_after}')
549-
print(f'After flush, IPv6 routes: \n{route_after}')
572+
logging.info(f'After flush, IPv6 addresses: \n{addr_after}')
573+
logging.info(f'After flush, IPv6 routes: \n{route_after}')
550574

551575

552576
class tcp_parameter:
@@ -575,28 +599,29 @@ def create_host_tcp_server(mytcp: tcp_parameter) -> None:
575599
AF_INET = socket.AF_INET6
576600
else:
577601
AF_INET = socket.AF_INET
578-
print('The host start to create a tcp server!')
602+
logging.info('The host start to create a tcp server!')
579603
sock = socket.socket(AF_INET, socket.SOCK_STREAM)
580604
sock.bind((mytcp.addr, mytcp.port))
581605
sock.listen(5)
582606
mytcp.listen_flag = True
583607

584-
print('The tcp server is waiting for connection!')
608+
logging.info('The tcp server is waiting for connection!')
585609
sock.settimeout(mytcp.timeout)
586610
connfd, addr = sock.accept()
587-
print('The tcp server connected with ', addr)
611+
logging.info(f'The tcp server connected with {addr}')
588612
mytcp.recv_flag = True
589613

590614
mytcp.tcp_bytes = connfd.recv(1024)
591-
print('The tcp server has received message: ', mytcp.tcp_bytes)
615+
tcp_str = str(mytcp.tcp_bytes)
616+
logging.info(f'The tcp server has received message: {tcp_str}')
592617

593618
except OSError:
594619
if mytcp.recv_flag:
595-
print('The tcp server did not receive message!')
620+
logging.error('The tcp server did not receive message!')
596621
else:
597-
print('The tcp server fail to connect!')
622+
logging.error('The tcp server fail to connect!')
598623
finally:
599-
print('Close the socket.')
624+
logging.info('Close the socket.')
600625
sock.close()
601626

602627

@@ -616,22 +641,19 @@ def decimal_to_hex(decimal_str: str) -> str:
616641
return hex_str
617642

618643

619-
def get_omrprefix(br: IdfDut) -> str:
620-
execute_command(br, 'br omrprefix')
621-
omrprefix = br.expect(r'Local: ((?:\w+:){4}):/\d+\r', timeout=5)[1].decode()
622-
return str(omrprefix)
644+
@extract_address('br omrprefix', r'Local: ((?:\w+:){4}):/\d+\r')
645+
def get_omrprefix(addr: str) -> str:
646+
return addr
623647

624648

625-
def get_onlinkprefix(br: IdfDut) -> str:
626-
execute_command(br, 'br onlinkprefix')
627-
onlinkprefix = br.expect(r'Local: ((?:\w+:){4}):/\d+\r', timeout=5)[1].decode()
628-
return str(onlinkprefix)
649+
@extract_address('br onlinkprefix', r'Local: ((?:\w+:){4}):/\d+\r')
650+
def get_onlinkprefix(addr: str) -> str:
651+
return addr
629652

630653

631-
def get_nat64prefix(br: IdfDut) -> str:
632-
execute_command(br, 'br nat64prefix')
633-
nat64prefix = br.expect(r'Local: ((?:\w+:){6}):/\d+', timeout=5)[1].decode()
634-
return str(nat64prefix)
654+
@extract_address('br nat64prefix', r'Local: ((?:\w+:){6}):/\d+')
655+
def get_nat64prefix(addr: str) -> str:
656+
return addr
635657

636658

637659
def execute_command(dut: IdfDut, command: str, prefix: str = 'ot ') -> None:
@@ -644,3 +666,17 @@ def get_ouput_string(dut: IdfDut, command: str, wait_time: int) -> str:
644666
tmp = dut.expect(pexpect.TIMEOUT, timeout=wait_time)
645667
clean_buffer(dut)
646668
return str(tmp)
669+
670+
671+
def wait_for_host_network(host: str = '8.8.8.8', retries: int = 6, interval: int = 10) -> None:
672+
for attempt in range(1, retries + 1):
673+
try:
674+
subprocess.run(['ping', '-c', '1', '-W', '2', host], check=True)
675+
logging.info(f'Host network reachable on attempt {attempt}')
676+
return
677+
except subprocess.CalledProcessError:
678+
logging.info(f'Ping attempt {attempt} failed, retrying in {interval} seconds...')
679+
if attempt < retries:
680+
time.sleep(interval)
681+
else:
682+
raise RuntimeError(f'Host network is not reachable after {retries} attempts.')

0 commit comments

Comments
 (0)