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
56import os
67import re
78import socket
89import struct
910import subprocess
1011import time
11- from collections .abc import Callable
1212from functools import wraps
13+ from typing import Callable
14+ from typing import Optional
15+ from typing import Tuple
1316
1417import netifaces
1518import pexpect
1821
1922
2023def 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+
175198def 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
222245def 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
521545def 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
532556def 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
552576class 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
637659def 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