mirror of
https://github.com/espressif/openthread.git
synced 2026-10-12 10:47:43 +00:00
[thread-cert] refine CLI output parsing (#7084)
This commit is contained in:
@@ -317,7 +317,7 @@ int main(int argc, char *argv[])
|
|||||||
rl_instream = stdin;
|
rl_instream = stdin;
|
||||||
rl_outstream = stdout;
|
rl_outstream = stdout;
|
||||||
rl_inhibit_completion = true;
|
rl_inhibit_completion = true;
|
||||||
rl_callback_handler_install("> ", InputCallback);
|
rl_callback_handler_install("", InputCallback);
|
||||||
rl_already_prompted = 1;
|
rl_already_prompted = 1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -558,27 +558,6 @@ class NodeImpl:
|
|||||||
def _expect_done(self, timeout=-1):
|
def _expect_done(self, timeout=-1):
|
||||||
self._expect('Done', timeout)
|
self._expect('Done', timeout)
|
||||||
|
|
||||||
def _prepare_pattern(self, pattern):
|
|
||||||
"""Build a new pexpect pattern matching line by line.
|
|
||||||
|
|
||||||
Adds lookahead and lookbehind to make each pattern match a whole line,
|
|
||||||
and add 'Done' as the first pattern.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pattern: a single regex or a list of regex.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A list of regex.
|
|
||||||
"""
|
|
||||||
EXPECT_LINE_FORMAT = r'(?<=[\r\n])%s(?=[\r\n])'
|
|
||||||
|
|
||||||
if isinstance(pattern, list):
|
|
||||||
pattern = [EXPECT_LINE_FORMAT % p for p in pattern]
|
|
||||||
else:
|
|
||||||
pattern = [EXPECT_LINE_FORMAT % pattern]
|
|
||||||
|
|
||||||
return [EXPECT_LINE_FORMAT % 'Done'] + pattern
|
|
||||||
|
|
||||||
def _expect_result(self, pattern, *args, **kwargs):
|
def _expect_result(self, pattern, *args, **kwargs):
|
||||||
"""Expect a single matching result.
|
"""Expect a single matching result.
|
||||||
|
|
||||||
@@ -599,34 +578,25 @@ class NodeImpl:
|
|||||||
Returns:
|
Returns:
|
||||||
The matched lines.
|
The matched lines.
|
||||||
"""
|
"""
|
||||||
results = []
|
output = self._expect_command_output()
|
||||||
pattern = self._prepare_pattern(pattern)
|
results = [line for line in output if self._match_pattern(line, pattern)]
|
||||||
|
|
||||||
while self._expect(pattern, *args, **kwargs):
|
|
||||||
results.append(self.pexpect.match.group(0).decode('utf8'))
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _expect_command_output(self, cmd: str, ignore_logs=True):
|
@staticmethod
|
||||||
|
def _match_pattern(line, pattern):
|
||||||
|
if isinstance(pattern, str):
|
||||||
|
pattern = re.compile(pattern)
|
||||||
|
|
||||||
|
if isinstance(pattern, re.Pattern):
|
||||||
|
return pattern.match(line)
|
||||||
|
else:
|
||||||
|
return any(NodeImpl._match_pattern(line, p) for p in pattern)
|
||||||
|
|
||||||
|
def _expect_command_output(self, ignore_logs=True):
|
||||||
lines = []
|
lines = []
|
||||||
cmd_output_started = False
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
self._expect(r"[^\n]+\n")
|
line = self.__readline(ignore_logs=ignore_logs)
|
||||||
line = self.pexpect.match.group(0).decode('utf8').strip()
|
|
||||||
|
|
||||||
if line.startswith('> '):
|
|
||||||
line = line[2:]
|
|
||||||
|
|
||||||
if line == '':
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line == cmd:
|
|
||||||
cmd_output_started = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not cmd_output_started or (ignore_logs and self.__is_logging_line(line)):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line == 'Done':
|
if line == 'Done':
|
||||||
break
|
break
|
||||||
@@ -635,7 +605,7 @@ class NodeImpl:
|
|||||||
else:
|
else:
|
||||||
lines.append(line)
|
lines.append(line)
|
||||||
|
|
||||||
print(f'_expect_command_output({cmd!r}) returns {lines!r}')
|
print(f'_expect_command_output() returns {lines!r}')
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def __is_logging_line(self, line: str) -> bool:
|
def __is_logging_line(self, line: str) -> bool:
|
||||||
@@ -690,13 +660,41 @@ class NodeImpl:
|
|||||||
assert len(payload) == payload_len
|
assert len(payload) == payload_len
|
||||||
return (direction, type, payload)
|
return (direction, type, payload)
|
||||||
|
|
||||||
def send_command(self, cmd, go=True):
|
def send_command(self, cmd, go=True, expect_command_echo=True):
|
||||||
print("%d: %s" % (self.nodeid, cmd))
|
print("%d: %s" % (self.nodeid, cmd))
|
||||||
self.pexpect.send(cmd + '\n')
|
self.pexpect.send(cmd + '\n')
|
||||||
if go:
|
if go:
|
||||||
self.simulator.go(0, nodeid=self.nodeid)
|
self.simulator.go(0, nodeid=self.nodeid)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
if expect_command_echo:
|
||||||
|
self._expect_command_echo(cmd)
|
||||||
|
|
||||||
|
def _expect_command_echo(self, cmd):
|
||||||
|
cmd = cmd.strip()
|
||||||
|
while True:
|
||||||
|
line = self.__readline()
|
||||||
|
if line == cmd:
|
||||||
|
break
|
||||||
|
|
||||||
|
logging.warning("expecting echo %r, but read %r", cmd, line)
|
||||||
|
|
||||||
|
def __readline(self, ignore_logs=True):
|
||||||
|
PROMPT = 'spinel-cli > ' if self.node_type == 'ncp-sim' else '> '
|
||||||
|
while True:
|
||||||
|
self._expect(r"[^\n]+\n")
|
||||||
|
line = self.pexpect.match.group(0).decode('utf8').strip()
|
||||||
|
while line.startswith(PROMPT):
|
||||||
|
line = line[len(PROMPT):]
|
||||||
|
|
||||||
|
if line == '':
|
||||||
|
continue
|
||||||
|
|
||||||
|
if ignore_logs and self.__is_logging_line(line):
|
||||||
|
continue
|
||||||
|
|
||||||
|
return line
|
||||||
|
|
||||||
def get_commands(self):
|
def get_commands(self):
|
||||||
self.send_command('?')
|
self.send_command('?')
|
||||||
self._expect('Commands:')
|
self._expect('Commands:')
|
||||||
@@ -848,7 +846,7 @@ class NodeImpl:
|
|||||||
|
|
||||||
cmd = 'srp server host'
|
cmd = 'srp server host'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
lines = self._expect_command_output(cmd)
|
lines = self._expect_command_output()
|
||||||
host_list = []
|
host_list = []
|
||||||
while lines:
|
while lines:
|
||||||
host = {}
|
host = {}
|
||||||
@@ -904,7 +902,7 @@ class NodeImpl:
|
|||||||
|
|
||||||
cmd = 'srp server service'
|
cmd = 'srp server service'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
lines = self._expect_command_output(cmd)
|
lines = self._expect_command_output()
|
||||||
service_list = []
|
service_list = []
|
||||||
while lines:
|
while lines:
|
||||||
service = {}
|
service = {}
|
||||||
@@ -975,12 +973,12 @@ class NodeImpl:
|
|||||||
def srp_client_get_state(self):
|
def srp_client_get_state(self):
|
||||||
cmd = 'srp client state'
|
cmd = 'srp client state'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._expect_command_output(cmd)[0]
|
return self._expect_command_output()[0]
|
||||||
|
|
||||||
def srp_client_get_auto_start_mode(self):
|
def srp_client_get_auto_start_mode(self):
|
||||||
cmd = 'srp client autostart'
|
cmd = 'srp client autostart'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._expect_command_output(cmd)[0]
|
return self._expect_command_output()[0]
|
||||||
|
|
||||||
def srp_client_enable_auto_start_mode(self):
|
def srp_client_enable_auto_start_mode(self):
|
||||||
self.send_command(f'srp client autostart enable')
|
self.send_command(f'srp client autostart enable')
|
||||||
@@ -993,17 +991,17 @@ class NodeImpl:
|
|||||||
def srp_client_get_server_address(self):
|
def srp_client_get_server_address(self):
|
||||||
cmd = 'srp client server address'
|
cmd = 'srp client server address'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._expect_command_output(cmd)[0]
|
return self._expect_command_output()[0]
|
||||||
|
|
||||||
def srp_client_get_server_port(self):
|
def srp_client_get_server_port(self):
|
||||||
cmd = 'srp client server port'
|
cmd = 'srp client server port'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return int(self._expect_command_output(cmd)[0])
|
return int(self._expect_command_output()[0])
|
||||||
|
|
||||||
def srp_client_get_host_state(self):
|
def srp_client_get_host_state(self):
|
||||||
cmd = 'srp client host state'
|
cmd = 'srp client host state'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._expect_command_output(cmd)[0]
|
return self._expect_command_output()[0]
|
||||||
|
|
||||||
def srp_client_set_host_name(self, name):
|
def srp_client_set_host_name(self, name):
|
||||||
self.send_command(f'srp client host name {name}')
|
self.send_command(f'srp client host name {name}')
|
||||||
@@ -1046,7 +1044,7 @@ class NodeImpl:
|
|||||||
def srp_client_get_services(self):
|
def srp_client_get_services(self):
|
||||||
cmd = 'srp client service'
|
cmd = 'srp client service'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
service_lines = self._expect_command_output(cmd)
|
service_lines = self._expect_command_output()
|
||||||
return [self._parse_srp_client_service(line) for line in service_lines]
|
return [self._parse_srp_client_service(line) for line in service_lines]
|
||||||
|
|
||||||
def _encode_txt_entry(self, entry):
|
def _encode_txt_entry(self, entry):
|
||||||
@@ -1086,7 +1084,7 @@ class NodeImpl:
|
|||||||
cmd = 'locate ' + anycast_addr
|
cmd = 'locate ' + anycast_addr
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
self.simulator.go(5)
|
self.simulator.go(5)
|
||||||
return self._parse_locate_result(self._expect_command_output(cmd)[0])
|
return self._parse_locate_result(self._expect_command_output()[0])
|
||||||
|
|
||||||
def _parse_locate_result(self, line: str):
|
def _parse_locate_result(self, line: str):
|
||||||
"""Parse anycast locate result as list of ml-eid and rloc16.
|
"""Parse anycast locate result as list of ml-eid and rloc16.
|
||||||
@@ -1238,7 +1236,7 @@ class NodeImpl:
|
|||||||
cmd += f' {int(timeout)}'
|
cmd += f' {int(timeout)}'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
self.simulator.go(3)
|
self.simulator.go(3)
|
||||||
lines = self._expect_command_output(cmd)
|
lines = self._expect_command_output()
|
||||||
m = re.match(r'status (\d+), (\d+) failed', lines[0])
|
m = re.match(r'status (\d+), (\d+) failed', lines[0])
|
||||||
assert m is not None, lines
|
assert m is not None, lines
|
||||||
status = int(m.group(1))
|
status = int(m.group(1))
|
||||||
@@ -1572,12 +1570,8 @@ class NodeImpl:
|
|||||||
def get_eidcaches(self):
|
def get_eidcaches(self):
|
||||||
eidcaches = []
|
eidcaches = []
|
||||||
self.send_command('eidcache')
|
self.send_command('eidcache')
|
||||||
|
for line in self._expect_results(r'([a-fA-F0-9\:]+) ([a-fA-F0-9]+)'):
|
||||||
pattern = self._prepare_pattern(r'([a-fA-F0-9\:]+) ([a-fA-F0-9]+)')
|
eidcaches.append(line.split())
|
||||||
while self._expect(pattern):
|
|
||||||
eid = self.pexpect.match.groups()[0].decode("utf-8")
|
|
||||||
rloc = self.pexpect.match.groups()[1].decode("utf-8")
|
|
||||||
eidcaches.append((eid, rloc))
|
|
||||||
|
|
||||||
return eidcaches
|
return eidcaches
|
||||||
|
|
||||||
@@ -1599,7 +1593,7 @@ class NodeImpl:
|
|||||||
"""Get the table of attached children."""
|
"""Get the table of attached children."""
|
||||||
cmd = 'child table'
|
cmd = 'child table'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
output = self._expect_command_output(cmd)
|
output = self._expect_command_output()
|
||||||
|
|
||||||
#
|
#
|
||||||
# Example output:
|
# Example output:
|
||||||
@@ -1787,12 +1781,12 @@ class NodeImpl:
|
|||||||
def get_omr_prefix(self):
|
def get_omr_prefix(self):
|
||||||
cmd = 'br omrprefix'
|
cmd = 'br omrprefix'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._expect_command_output(cmd)[0]
|
return self._expect_command_output()[0]
|
||||||
|
|
||||||
def get_on_link_prefix(self):
|
def get_on_link_prefix(self):
|
||||||
cmd = 'br onlinkprefix'
|
cmd = 'br onlinkprefix'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._expect_command_output(cmd)[0]
|
return self._expect_command_output()[0]
|
||||||
|
|
||||||
def get_prefixes(self):
|
def get_prefixes(self):
|
||||||
return self.get_netdata()['Prefixes']
|
return self.get_netdata()['Prefixes']
|
||||||
@@ -1814,7 +1808,7 @@ class NodeImpl:
|
|||||||
|
|
||||||
def netdata_show(self):
|
def netdata_show(self):
|
||||||
self.send_command('netdata show')
|
self.send_command('netdata show')
|
||||||
return self._expect_command_output('netdata show')
|
return self._expect_command_output()
|
||||||
|
|
||||||
def get_netdata(self):
|
def get_netdata(self):
|
||||||
raw_netdata = self.netdata_show()
|
raw_netdata = self.netdata_show()
|
||||||
@@ -1970,8 +1964,16 @@ class NodeImpl:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.send_command('reset')
|
self.send_command('reset', expect_command_echo=False)
|
||||||
time.sleep(self.RESET_DELAY)
|
time.sleep(self.RESET_DELAY)
|
||||||
|
# Send a "version" command and drain the CLI output after reset
|
||||||
|
self.send_command('version', expect_command_echo=False)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self._expect(r"[^\n]+\n", timeout=0.1)
|
||||||
|
continue
|
||||||
|
except pexpect.TIMEOUT:
|
||||||
|
break
|
||||||
|
|
||||||
if self.is_otbr:
|
if self.is_otbr:
|
||||||
self.set_log_level(5)
|
self.set_log_level(5)
|
||||||
@@ -2664,7 +2666,7 @@ class NodeImpl:
|
|||||||
"""
|
"""
|
||||||
cmd = f'dns config'
|
cmd = f'dns config'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
output = self._expect_command_output(cmd)
|
output = self._expect_command_output()
|
||||||
config = {}
|
config = {}
|
||||||
for line in output:
|
for line in output:
|
||||||
k, v = line.split(': ')
|
k, v = line.split(': ')
|
||||||
@@ -2683,7 +2685,7 @@ class NodeImpl:
|
|||||||
|
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
self.simulator.go(10)
|
self.simulator.go(10)
|
||||||
output = self._expect_command_output(cmd)
|
output = self._expect_command_output()
|
||||||
dns_resp = output[0]
|
dns_resp = output[0]
|
||||||
# example output: "DNS response for host1.default.service.arpa. - fd00:db8:0:0:fd3d:d471:1e8c:b60 TTL:7190 "
|
# example output: "DNS response for host1.default.service.arpa. - fd00:db8:0:0:fd3d:d471:1e8c:b60 TTL:7190 "
|
||||||
# " fd00:db8:0:0:0:ff:fe00:9000 TTL:7190"
|
# " fd00:db8:0:0:0:ff:fe00:9000 TTL:7190"
|
||||||
@@ -2716,7 +2718,7 @@ class NodeImpl:
|
|||||||
|
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
self.simulator.go(10)
|
self.simulator.go(10)
|
||||||
output = self._expect_command_output(cmd)
|
output = self._expect_command_output()
|
||||||
|
|
||||||
# Example output:
|
# Example output:
|
||||||
# DNS service resolution response for ins2 for service _ipps._tcp.default.service.arpa.
|
# DNS service resolution response for ins2 for service _ipps._tcp.default.service.arpa.
|
||||||
@@ -2786,7 +2788,7 @@ class NodeImpl:
|
|||||||
|
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
self.simulator.go(10)
|
self.simulator.go(10)
|
||||||
output = '\n'.join(self._expect_command_output(cmd))
|
output = '\n'.join(self._expect_command_output())
|
||||||
|
|
||||||
# Example output:
|
# Example output:
|
||||||
# ins2
|
# ins2
|
||||||
@@ -2822,7 +2824,7 @@ class NodeImpl:
|
|||||||
def set_mliid(self, mliid: str):
|
def set_mliid(self, mliid: str):
|
||||||
cmd = f'mliid {mliid}'
|
cmd = f'mliid {mliid}'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
self._expect_command_output(cmd)
|
self._expect_command_output()
|
||||||
|
|
||||||
def history_netinfo(self, num_entries=0):
|
def history_netinfo(self, num_entries=0):
|
||||||
"""
|
"""
|
||||||
@@ -2849,7 +2851,7 @@ class NodeImpl:
|
|||||||
"""
|
"""
|
||||||
cmd = f'history netinfo list {num_entries}'
|
cmd = f'history netinfo list {num_entries}'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
output = self._expect_command_output(cmd)
|
output = self._expect_command_output()
|
||||||
netinfos = []
|
netinfos = []
|
||||||
for entry in output:
|
for entry in output:
|
||||||
netinfo = {}
|
netinfo = {}
|
||||||
@@ -2884,7 +2886,7 @@ class NodeImpl:
|
|||||||
"""
|
"""
|
||||||
cmd = f'history rx list {num_entries}'
|
cmd = f'history rx list {num_entries}'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._parse_history_rx_tx_ouput(self._expect_command_output(cmd))
|
return self._parse_history_rx_tx_ouput(self._expect_command_output())
|
||||||
|
|
||||||
def history_tx(self, num_entries=0):
|
def history_tx(self, num_entries=0):
|
||||||
"""
|
"""
|
||||||
@@ -2910,7 +2912,7 @@ class NodeImpl:
|
|||||||
"""
|
"""
|
||||||
cmd = f'history tx list {num_entries}'
|
cmd = f'history tx list {num_entries}'
|
||||||
self.send_command(cmd)
|
self.send_command(cmd)
|
||||||
return self._parse_history_rx_tx_ouput(self._expect_command_output(cmd))
|
return self._parse_history_rx_tx_ouput(self._expect_command_output())
|
||||||
|
|
||||||
def _parse_history_rx_tx_ouput(self, lines):
|
def _parse_history_rx_tx_ouput(self, lines):
|
||||||
rxtx_list = []
|
rxtx_list = []
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class TestDiag(thread_cert.TestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
for case in cases:
|
for case in cases:
|
||||||
node.send_command(case[0])
|
node.send_command(case[0], expect_command_echo=False)
|
||||||
self.simulator.go(1)
|
self.simulator.go(1)
|
||||||
if type(self.simulator).__name__ == 'VirtualTime':
|
if type(self.simulator).__name__ == 'VirtualTime':
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|||||||
Reference in New Issue
Block a user