Skip to content

Commit cf15516

Browse files
committed
Merge branch 'feature/higher_level_com_gdb' into 'master'
CI: Use higher-level interaction with GDB in example tests and test apps Closes IDF-1622 See merge request espressif/esp-idf!8840
2 parents 4288a59 + 493c852 commit cf15516

File tree

10 files changed

+165
-151
lines changed

10 files changed

+165
-151
lines changed

examples/storage/semihost_vfs/semihost_vfs_example_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def test_examples_semihost_vfs(env, extra_data):
2424
temp_dir = tempfile.mkdtemp()
2525
host_file_path = os.path.join(proj_path, 'data', host_file_name)
2626
shutil.copyfile(host_file_path, os.path.join(temp_dir, host_file_name))
27-
openocd_extra_args = '-c \'set ESP_SEMIHOST_BASEDIR {}\''.format(temp_dir)
27+
cfg_cmds = ['set ESP_SEMIHOST_BASEDIR "{}"'.format(temp_dir)]
2828

29-
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log'), openocd_extra_args):
29+
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target, cfg_cmds=cfg_cmds):
3030
dut.start_app()
3131
dut.expect_all('example: Switch to semihosted stdout',
3232
'example: Switched back to UART stdout',

examples/system/app_trace_to_host/example_test.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,22 @@ def test_examples_app_trace_to_host(env, extra_data):
1212
idf_path = dut.app.get_sdk_path()
1313
proj_path = os.path.join(idf_path, rel_project_path)
1414

15-
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log')):
16-
with ttfw_idf.TelnetProcess(os.path.join(proj_path, 'telnet.log')) as telnet_p:
17-
dut.start_app()
18-
dut.expect_all('example: Enabling ADC1 on channel 6 / GPIO34.',
19-
'example: Enabling CW generator on DAC channel 1',
20-
'example: Custom divider of RTC 8 MHz clock has been set.',
21-
'example: Sampling ADC and sending data to the host...',
22-
re.compile(r'example: Collected \d+ samples in 20 ms.'),
23-
'example: Sampling ADC and sending data to the UART...',
24-
re.compile(r'example: Sample:\d, Value:\d+'),
25-
re.compile(r'example: Collected \d+ samples in 20 ms.'),
26-
timeout=20)
27-
28-
telnet_p.pexpect_proc.sendline('esp apptrace start file://adc.log 0 9000 5 0 0')
29-
telnet_p.pexpect_proc.expect_exact('App trace params: from 2 cores, size 9000 bytes, '
30-
'stop_tmo 5 s, poll period 0 ms, wait_rst 0, skip 0 bytes')
31-
telnet_p.pexpect_proc.expect_exact('Targets connected.')
32-
telnet_p.pexpect_proc.expect_exact('Targets disconnected.')
33-
telnet_p.pexpect_proc.expect_exact('Tracing is STOPPED. Size is 9000 of 9000 @')
34-
telnet_p.pexpect_proc.expect_exact('Data: blocks incomplete 0, lost bytes: 0')
15+
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target) as ocd:
16+
dut.start_app()
17+
dut.expect_all('example: Enabling ADC1 on channel 6 / GPIO34.',
18+
'example: Enabling CW generator on DAC channel 1',
19+
'example: Custom divider of RTC 8 MHz clock has been set.',
20+
'example: Sampling ADC and sending data to the host...',
21+
re.compile(r'example: Collected \d+ samples in 20 ms.'),
22+
'example: Sampling ADC and sending data to the UART...',
23+
re.compile(r'example: Sample:\d, Value:\d+'),
24+
re.compile(r'example: Collected \d+ samples in 20 ms.'),
25+
timeout=20)
26+
27+
response = ocd.cmd_exec('esp apptrace start file://adc.log 0 9000 5 0 0')
28+
with open(os.path.join(proj_path, 'telnet.log'), 'w') as f:
29+
f.write(response)
30+
assert('Data: blocks incomplete 0, lost bytes: 0' in response)
3531

3632
with ttfw_idf.CustomProcess(' '.join([os.path.join(idf_path, 'tools/esp_app_trace/logtrace_proc.py'),
3733
'adc.log',

examples/system/gcov/example_test.py

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from __future__ import unicode_literals
2-
from pexpect import TIMEOUT
32
from ttfw_idf import Utility
43
import os
54
import ttfw_idf
@@ -12,41 +11,46 @@ def test_examples_gcov(env, extra_data):
1211
dut = env.get_dut('gcov', rel_project_path)
1312
idf_path = dut.app.get_sdk_path()
1413
proj_path = os.path.join(idf_path, rel_project_path)
15-
16-
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log')):
17-
with ttfw_idf.TelnetProcess(os.path.join(proj_path, 'telnet.log')) as telnet_p:
18-
dut.start_app()
19-
20-
def expect_counter_output(loop, timeout=10):
21-
dut.expect_all('blink_dummy_func: Counter = {}'.format(loop),
22-
'some_dummy_func: Counter = {}'.format(loop * 2),
23-
timeout=timeout)
24-
25-
expect_counter_output(0, timeout=20)
26-
dut.expect('Ready to dump GCOV data...', timeout=5)
27-
28-
def dump_coverage():
29-
try:
30-
telnet_p.pexpect_proc.sendline('esp gcov dump')
31-
telnet_p.pexpect_proc.expect_exact('Targets connected.')
32-
telnet_p.pexpect_proc.expect_exact('gcov_example_main.c.gcda')
33-
telnet_p.pexpect_proc.expect_exact('gcov_example_func.c.gcda')
34-
telnet_p.pexpect_proc.expect_exact('some_funcs.c.gcda')
35-
telnet_p.pexpect_proc.expect_exact('Targets disconnected.')
36-
except TIMEOUT:
37-
# Print what is happening with DUT. Limit the size if it is in loop and generating output.
38-
Utility.console_log(dut.read(size=1000))
39-
raise
40-
41-
dump_coverage()
42-
dut.expect('GCOV data have been dumped.', timeout=5)
43-
expect_counter_output(1)
44-
dut.expect('Ready to dump GCOV data...', timeout=5)
45-
dump_coverage()
46-
dut.expect('GCOV data have been dumped.', timeout=5)
47-
48-
for i in range(2, 6):
49-
expect_counter_output(i)
14+
openocd_cmd_log = os.path.join(proj_path, 'openocd_cmd.log')
15+
16+
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target) as oocd:
17+
dut.start_app()
18+
19+
def expect_counter_output(loop, timeout=10):
20+
dut.expect_all('blink_dummy_func: Counter = {}'.format(loop),
21+
'some_dummy_func: Counter = {}'.format(loop * 2),
22+
timeout=timeout)
23+
24+
expect_counter_output(0, timeout=20)
25+
dut.expect('Ready to dump GCOV data...', timeout=5)
26+
27+
def dump_coverage():
28+
try:
29+
response = oocd.cmd_exec('esp gcov dump')
30+
with open(openocd_cmd_log, 'a') as f:
31+
f.write(response)
32+
33+
assert all(x in response for x in ['Targets connected.',
34+
'gcov_example_main.c.gcda',
35+
'gcov_example_func.c.gcda',
36+
'some_funcs.c.gcda',
37+
'Targets disconnected.',
38+
])
39+
40+
except AssertionError:
41+
# Print what is happening with DUT. Limit the size if it is in loop and generating output.
42+
Utility.console_log(dut.read(size=1000))
43+
raise
44+
45+
dump_coverage()
46+
dut.expect('GCOV data have been dumped.', timeout=5)
47+
expect_counter_output(1)
48+
dut.expect('Ready to dump GCOV data...', timeout=5)
49+
dump_coverage()
50+
dut.expect('GCOV data have been dumped.', timeout=5)
51+
52+
for i in range(2, 6):
53+
expect_counter_output(i)
5054

5155

5256
if __name__ == '__main__':

examples/system/sysview_tracing/example_test.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from __future__ import unicode_literals
22
from io import open
3+
import debug_backend
34
import os
45
import re
56
import tempfile
7+
import time
68
import ttfw_idf
79

810

@@ -29,25 +31,28 @@ def get_temp_file():
2931
new_content = new_content.replace('file:///tmp/sysview_example.svdat', 'file://{}'.format(tempfiles[1]), 1)
3032
f_out.write(new_content)
3133

32-
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log')):
34+
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target) as oocd:
3335
dut.start_app()
3436

3537
def dut_expect_task_event():
3638
dut.expect(re.compile(r'example: Task\[0x3[0-9A-Fa-f]+\]: received event \d+'), timeout=30)
3739

3840
dut_expect_task_event()
3941

40-
gdb_args = '-x {} --directory={}'.format(tempfiles[0], os.path.join(proj_path, 'main'))
41-
with ttfw_idf.GDBProcess(os.path.join(proj_path, 'gdb.log'), elf_path, dut.app.target, gdb_args) as gdb:
42-
gdb.pexpect_proc.expect_exact('Thread 1 hit Breakpoint 1, app_main ()')
43-
gdb.pexpect_proc.expect_exact('Targets connected.')
44-
gdb.pexpect_proc.expect(re.compile(r'\d+'))
42+
gdb_log = os.path.join(proj_path, 'gdb.log')
43+
gdb_workdir = os.path.join(proj_path, 'main')
44+
with ttfw_idf.GDBBackend(gdb_log, elf_path, dut.app.target, tempfiles[0], gdb_workdir) as p:
45+
p.gdb.wait_target_state(debug_backend.TARGET_STATE_RUNNING)
46+
stop_reason = p.gdb.wait_target_state(debug_backend.TARGET_STATE_STOPPED)
47+
assert stop_reason == debug_backend.TARGET_STOP_REASON_BP, 'STOP reason: {}'.format(stop_reason)
4548

4649
dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect()
4750
dut_expect_task_event()
4851

49-
gdb.pexpect_proc.sendcontrol('c')
50-
gdb.pexpect_proc.expect_exact('(gdb)')
52+
# Do a sleep while sysview samples are captured.
53+
time.sleep(3)
54+
# GDBMI isn't responding now to any commands, therefore, the following command is issued to openocd
55+
oocd.cmd_exec('esp sysview stop')
5156
finally:
5257
for x in tempfiles:
5358
try:

examples/system/sysview_tracing_heap_log/example_test.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22
from io import open
3+
import debug_backend
34
import os
45
import re
56
import tempfile
@@ -29,14 +30,17 @@ def get_temp_file():
2930
new_content = new_content.replace('file:///tmp/heap_log.svdat', 'file://{}'.format(tempfiles[1]), 1)
3031
f_out.write(new_content)
3132

32-
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log')):
33+
with ttfw_idf.OCDBackend(os.path.join(proj_path, 'openocd.log'), dut.app.target):
3334
dut.start_app()
3435
dut.expect('esp_apptrace: Initialized TRAX on CPU0')
3536

36-
gdb_args = '-x {} --directory={}'.format(tempfiles[0], os.path.join(proj_path, 'main'))
37-
with ttfw_idf.GDBProcess(os.path.join(proj_path, 'gdb.log'), elf_path, dut.app.target, gdb_args) as gdb:
38-
gdb.pexpect_proc.expect_exact('Thread 1 hit Temporary breakpoint 2, heap_trace_stop ()')
39-
gdb.pexpect_proc.expect_exact('(gdb)')
37+
gdb_log = os.path.join(proj_path, 'gdb.log')
38+
gdb_workdir = os.path.join(proj_path, 'main')
39+
with ttfw_idf.GDBBackend(gdb_log, elf_path, dut.app.target, tempfiles[0], gdb_workdir) as p:
40+
for _ in range(2): # There are two breakpoints
41+
p.gdb.wait_target_state(debug_backend.TARGET_STATE_RUNNING)
42+
stop_reason = p.gdb.wait_target_state(debug_backend.TARGET_STATE_STOPPED)
43+
assert stop_reason == debug_backend.TARGET_STOP_REASON_BP, 'STOP reason: {}'.format(stop_reason)
4044

4145
# dut has been restarted by gdb since the last dut.expect()
4246
dut.expect('esp_apptrace: Initialized TRAX on CPU0')

tools/ci/config/target-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ example_test_009:
305305
expire_in: 1 week
306306
variables:
307307
SETUP_TOOLS: "1"
308+
PYTHON_VER: 3
308309

309310
example_test_010:
310311
extends: .example_test_template
@@ -368,6 +369,7 @@ test_app_test_001:
368369
expire_in: 1 week
369370
variables:
370371
SETUP_TOOLS: "1"
372+
PYTHON_VER: 3
371373

372374
test_app_test_002:
373375
extends: .test_app_template

tools/ci/python_packages/ttfw_idf/DebugUtils.py

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
from __future__ import unicode_literals
1616
from io import open
1717
from tiny_test_fw import Utility
18+
import debug_backend
19+
import logging
1820
import pexpect
21+
import pygdbmi.gdbcontroller
1922

2023

2124
class CustomProcess(object):
@@ -37,63 +40,68 @@ def __exit__(self, type, value, traceback):
3740
self.f.close()
3841

3942

40-
class OCDProcess(CustomProcess):
41-
def __init__(self, logfile_path, extra_args='', verbose=True):
43+
class OCDBackend(object):
44+
def __init__(self, logfile_path, target, cfg_cmds=[], extra_args=[]):
4245
# TODO Use configuration file implied by the test environment (board)
43-
cmd = 'openocd {} -f board/esp32-wrover-kit-3.3v.cfg'.format(extra_args)
44-
super(OCDProcess, self).__init__(cmd, logfile_path, verbose)
45-
patterns = ['Info : Listening on port 3333 for gdb connections']
46+
self.oocd = debug_backend.create_oocd(chip_name=target,
47+
oocd_exec='openocd',
48+
oocd_scripts=None,
49+
oocd_cfg_files=['board/esp32-wrover-kit-3.3v.cfg'],
50+
oocd_cfg_cmds=cfg_cmds,
51+
oocd_debug=2,
52+
oocd_args=extra_args,
53+
host='localhost',
54+
log_level=logging.DEBUG,
55+
log_stream_handler=None,
56+
log_file_handler=logging.FileHandler(logfile_path, 'w'),
57+
scope=None)
58+
self.oocd.start()
4659

47-
try:
48-
while True:
49-
i = self.pexpect_proc.expect_exact(patterns, timeout=30)
50-
# TIMEOUT or EOF exceptions will be thrown upon other errors
51-
if i == 0:
52-
if self.verbose:
53-
Utility.console_log('openocd is listening for gdb connections')
54-
break # success
55-
except Exception:
56-
if self.verbose:
57-
Utility.console_log('openocd initialization has failed', 'R')
58-
raise
59-
60-
def close(self):
61-
try:
62-
self.pexpect_proc.sendcontrol('c')
63-
self.pexpect_proc.expect_exact('shutdown command invoked')
64-
except Exception:
65-
if self.verbose:
66-
Utility.console_log('openocd needs to be killed', 'O')
67-
super(OCDProcess, self).close()
68-
69-
70-
class GDBProcess(CustomProcess):
71-
def __init__(self, logfile_path, elffile_path, target, extra_args='', verbose=True):
72-
cmd = 'xtensa-{}-elf-gdb {} {}'.format(target, extra_args, elffile_path)
73-
super(GDBProcess, self).__init__(cmd, logfile_path, verbose)
74-
75-
def close(self):
76-
try:
77-
self.pexpect_proc.sendline('q')
78-
self.pexpect_proc.expect_exact('Quit anyway? (y or n)')
79-
self.pexpect_proc.sendline('y')
80-
self.pexpect_proc.expect_exact('Ending remote debugging.')
81-
except Exception:
82-
if self.verbose:
83-
Utility.console_log('gdb needs to be killed', 'O')
84-
super(GDBProcess, self).close()
60+
def __enter__(self):
61+
return self
8562

63+
def __exit__(self, type, value, traceback):
64+
self.oocd.stop()
65+
66+
def cmd_exec(self, cmd):
67+
return self.oocd.cmd_exec(cmd)
68+
69+
70+
class GDBBackend(object):
71+
def __init__(self, logfile_path, elffile_path, target, gdbinit_path=None, working_dir=None):
72+
self.gdb = debug_backend.create_gdb(chip_name=target,
73+
gdb_path='xtensa-{}-elf-gdb'.format(target),
74+
remote_target=None,
75+
extended_remote_mode=False,
76+
gdb_log_file=logfile_path,
77+
log_level=None,
78+
log_stream_handler=None,
79+
log_file_handler=None,
80+
scope=None)
81+
if working_dir:
82+
self.gdb.console_cmd_run('directory {}'.format(working_dir))
83+
self.gdb.exec_file_set(elffile_path)
84+
if gdbinit_path:
85+
try:
86+
self.gdb.console_cmd_run('source {}'.format(gdbinit_path))
87+
except debug_backend.defs.DebuggerTargetStateTimeoutError:
88+
# The internal timeout is not enough on RPI for more time consuming operations, e.g. "load".
89+
# So lets try to apply the commands one-by-one:
90+
with open(gdbinit_path, 'r') as f:
91+
for line in f:
92+
line = line.strip()
93+
if len(line) > 0 and not line.startswith('#'):
94+
self.gdb.console_cmd_run(line)
95+
# Note that some commands cannot be applied with console_cmd_run, e.g. "commands"
8696

87-
class TelnetProcess(CustomProcess):
88-
def __init__(self, logfile_path, host='localhost', port=4444, verbose=True):
89-
cmd = 'telnet {} {}'.format(host, port)
90-
super(TelnetProcess, self).__init__(cmd, logfile_path, verbose)
97+
def __enter__(self):
98+
return self
9199

92-
def close(self):
100+
def __exit__(self, type, value, traceback):
93101
try:
94-
self.pexpect_proc.sendline('exit')
95-
self.pexpect_proc.expect_exact('Connection closed by foreign host.')
96-
except Exception:
97-
if self.verbose:
98-
Utility.console_log('telnet needs to be killed', 'O')
99-
super(TelnetProcess, self).close()
102+
self.gdb.gdb_exit()
103+
except pygdbmi.gdbcontroller.NoGdbProcessError as e:
104+
# the debug backend can fail on gdb exit when it tries to read the response after issuing the exit command.
105+
Utility.console_log('Ignoring exception: {}'.format(e), 'O')
106+
except debug_backend.defs.DebuggerTargetStateTimeoutError:
107+
Utility.console_log('Ignoring timeout exception for GDB exit', 'O')

tools/ci/python_packages/ttfw_idf/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from tiny_test_fw import TinyFW, Utility
2121
from .IDFApp import IDFApp, Example, LoadableElfTestApp, UT, TestApp # noqa: export all Apps for users
2222
from .IDFDUT import IDFDUT, ESP32DUT, ESP32S2DUT, ESP8266DUT, ESP32QEMUDUT # noqa: export DUTs for users
23-
from .DebugUtils import OCDProcess, GDBProcess, TelnetProcess, CustomProcess # noqa: export DebugUtils for users
23+
from .DebugUtils import OCDBackend, GDBBackend, CustomProcess # noqa: export DebugUtils for users
2424

2525
# pass TARGET_DUT_CLS_DICT to Env.py to avoid circular dependency issue.
2626
TARGET_DUT_CLS_DICT = {

0 commit comments

Comments
 (0)