Merge branch 'ci/enable_sysview_tests_v5.2' into 'release/v5.2'

ci: enable sysview example tests for all chips (v5.2)

See merge request espressif/esp-idf!39475
This commit is contained in:
Jiang Jiang Jian
2025-07-28 14:28:54 +08:00
13 changed files with 292 additions and 405 deletions

View File

@@ -155,6 +155,18 @@ pytest_examples_esp32s2_jtag:
- .rules:test:example_test-esp32s2 - .rules:test:example_test-esp32s2
tags: [ esp32s2, jtag ] tags: [ esp32s2, jtag ]
pytest_examples_esp32c6_usb_serial_jtag:
extends:
- .pytest_examples_dir_jtag_template
- .rules:test:example_test-esp32c6
tags: [ esp32c6, usb_serial_jtag ]
pytest_examples_esp32h2_usb_serial_jtag:
extends:
- .pytest_examples_dir_jtag_template
- .rules:test:example_test-esp32h2
tags: [ esp32h2, usb_serial_jtag ]
pytest_examples_esp32s3_generic: pytest_examples_esp32s3_generic:
extends: extends:
- .pytest_examples_dir_template - .pytest_examples_dir_template

View File

@@ -1,8 +1,6 @@
# SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# pylint: disable=W0621 # redefined-outer-name # pylint: disable=W0621 # redefined-outer-name
# This file is a pytest root configuration file and provide the following functionalities: # This file is a pytest root configuration file and provide the following functionalities:
# 1. Defines a few fixtures that could be used under the whole project. # 1. Defines a few fixtures that could be used under the whole project.
# 2. Defines a few hook functions. # 2. Defines a few hook functions.
@@ -12,22 +10,30 @@
# #
# This is an experimental feature, and if you found any bug or have any question, please report to # This is an experimental feature, and if you found any bug or have any question, please report to
# https://github.com/espressif/pytest-embedded/issues # https://github.com/espressif/pytest-embedded/issues
import glob import glob
import json import json
import logging import logging
import os import os
import re import re
import signal
import sys import sys
import time
from copy import deepcopy from copy import deepcopy
from typing import Callable, Optional from typing import Any
from typing import Callable
from typing import Optional
import pexpect
import pytest import pytest
from _pytest.config import Config from _pytest.config import Config
from _pytest.fixtures import FixtureRequest from _pytest.fixtures import FixtureRequest
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture from pytest_embedded.plugin import multi_dut_argument
from pytest_embedded.plugin import multi_dut_fixture
from pytest_embedded.utils import to_bytes
from pytest_embedded.utils import to_str
from pytest_embedded_idf.dut import IdfDut from pytest_embedded_idf.dut import IdfDut
from pytest_embedded_idf.unity_tester import CaseTester from pytest_embedded_idf.unity_tester import CaseTester
from pytest_embedded_jtag._telnetlib.telnetlib import Telnet # python 3.13 removed telnetlib, use this instead
try: try:
from idf_ci_utils import IDF_PATH from idf_ci_utils import IDF_PATH
@@ -94,6 +100,113 @@ def test_case_name(request: FixtureRequest, target: str, config: str) -> str:
return format_case_id(target, config, request.node.originalname, is_qemu=is_qemu, params=filtered_params) # type: ignore return format_case_id(target, config, request.node.originalname, is_qemu=is_qemu, params=filtered_params) # type: ignore
class OpenOCD:
def __init__(self, dut: 'IdfDut'):
self.MAX_RETRIES = 3
self.RETRY_DELAY = 1
self.TELNET_PORT = 4444
self.dut = dut
self.telnet: Optional[Telnet] = None
self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
self.proc: Optional[pexpect.spawn] = None
def __enter__(self) -> 'OpenOCD':
return self
def __exit__(self, exception_type: Any, exception_value: Any, exception_traceback: Any) -> None:
self.kill()
def run(self) -> Optional['OpenOCD']:
desc_path = os.path.join(self.dut.app.binary_path, 'project_description.json')
try:
with open(desc_path, 'r') as f:
project_desc = json.load(f)
except FileNotFoundError:
logging.error('Project description file not found at %s', desc_path)
raise
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
raise RuntimeError('OPENOCD_SCRIPTS environment variable is not set.')
debug_args = project_desc.get('debug_arguments_openocd')
if not debug_args:
raise KeyError("'debug_arguments_openocd' key is missing in project_description.json")
# For debug purposes, make the value '4'
ocd_env = os.environ.copy()
ocd_env['LIBUSB_DEBUG'] = '1'
for _ in range(1, self.MAX_RETRIES + 1):
try:
self.proc = pexpect.spawn(
command='openocd',
args=['-s', openocd_scripts] + debug_args.split(),
timeout=5,
encoding='utf-8',
codec_errors='ignore',
env=ocd_env,
)
if self.proc and self.proc.isalive():
self.proc.expect_exact('Info : Listening on port 3333 for gdb connections', timeout=5)
self.connect_telnet()
self.write('log_output {}'.format(self.log_file))
return self
except (pexpect.exceptions.EOF, pexpect.exceptions.TIMEOUT, ConnectionRefusedError) as e:
logging.error('Error running OpenOCD: %s', str(e))
self.kill()
time.sleep(self.RETRY_DELAY)
raise RuntimeError('Failed to run OpenOCD after %d attempts.', self.MAX_RETRIES)
def connect_telnet(self) -> None:
for attempt in range(1, self.MAX_RETRIES + 1):
try:
self.telnet = Telnet('127.0.0.1', self.TELNET_PORT, 5)
break
except ConnectionRefusedError as e:
logging.error('Error telnet connection: %s in attempt:%d', e, attempt)
time.sleep(1)
else:
raise ConnectionRefusedError
def write(self, s: str) -> Any:
if self.telnet is None:
logging.error('Telnet connection is not established.')
return ''
resp = self.telnet.read_very_eager()
self.telnet.write(to_bytes(s, '\n'))
resp += self.telnet.read_until(b'>')
return to_str(resp)
def apptrace_wait_stop(self, timeout: int = 30) -> None:
stopped = False
end_before = time.time() + timeout
while not stopped:
cmd_out = self.write('esp apptrace status')
for line in cmd_out.splitlines():
if line.startswith('Tracing is STOPPED.'):
stopped = True
break
if not stopped and time.time() > end_before:
raise pexpect.TIMEOUT('Failed to wait for apptrace stop!')
time.sleep(1)
def kill(self) -> None:
# Check if the process is still running
if self.proc and self.proc.isalive():
self.proc.terminate()
self.proc.kill(signal.SIGKILL)
@pytest.fixture
def openocd_dut(dut: IdfDut) -> OpenOCD:
if isinstance(dut, tuple):
raise ValueError('Multi-DUT support is not implemented yet')
return OpenOCD(dut)
@pytest.fixture @pytest.fixture
@multi_dut_fixture @multi_dut_fixture
def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str: def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str:
@@ -314,7 +427,7 @@ def pytest_configure(config: Config) -> None:
print('Detected app: ', apps_list[-1]) print('Detected app: ', apps_list[-1])
else: else:
print( print(
f'WARNING: app_info base dir {app_info_basedir} not recognizable in {app_info["app_dir"]}, skipping...' f'WARNING: app_info base dir {app_info_basedir} not recognizable in {app_info["app_dir"]}, skipping...' # noqa: E713
) )
continue continue

View File

@@ -100,10 +100,7 @@ examples/system/gcov:
disable_test: disable_test:
- if: IDF_TARGET == "esp32p4" - if: IDF_TARGET == "esp32p4"
temporary: true temporary: true
reason: lack of runners reason: target esp32p4 is not supported yet
- if: IDF_TARGET == "esp32s3"
temporary: true
reason: unstable, known data corruption issue #TODO: OCD-1048
examples/system/gdbstub: examples/system/gdbstub:
disable: disable:
@@ -219,18 +216,10 @@ examples/system/select:
examples/system/sysview_tracing: examples/system/sysview_tracing:
disable: disable:
- if: SOC_GPTIMER_SUPPORTED != 1 - if: SOC_GPTIMER_SUPPORTED != 1
disable_test:
- if: IDF_TARGET != "esp32"
temporary: true
reason: lack of runners
examples/system/sysview_tracing_heap_log: examples/system/sysview_tracing_heap_log:
disable: disable:
- if: SOC_GPTIMER_SUPPORTED != 1 - if: SOC_GPTIMER_SUPPORTED != 1
disable_test:
- if: IDF_TARGET != "esp32"
temporary: true
reason: lack of runners
examples/system/task_watchdog: examples/system/task_watchdog:
enable: enable:

View File

@@ -1,125 +1,18 @@
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os
import signal
import time import time
from telnetlib import Telnet import typing
from typing import Any
from typing import Optional
import pexpect
import pytest import pytest
from pytest_embedded.utils import to_bytes
from pytest_embedded.utils import to_str
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
MAX_RETRIES = 3 if typing.TYPE_CHECKING:
RETRY_DELAY = 1 from conftest import OpenOCD
TELNET_PORT = 4444
class OpenOCD: def _test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
def __init__(self, dut: 'IdfDut'):
self.dut = dut
self.telnet: Optional[Telnet] = None
self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
self.proc: Optional[pexpect.spawn] = None
def run(self) -> Optional['OpenOCD']:
desc_path = os.path.join(self.dut.app.binary_path, 'project_description.json')
try:
with open(desc_path, 'r') as f:
project_desc = json.load(f)
except FileNotFoundError:
logging.error('Project description file not found at %s', desc_path)
return None
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
logging.error('OPENOCD_SCRIPTS environment variable is not set.')
return None
debug_args = project_desc.get('debug_arguments_openocd')
if not debug_args:
logging.error("'debug_arguments_openocd' key is missing in project_description.json")
return None
# For debug purposes, make the value '4'
ocd_env = os.environ.copy()
ocd_env['LIBUSB_DEBUG'] = '1'
for _ in range(1, MAX_RETRIES + 1):
try:
self.proc = pexpect.spawn(
command='openocd',
args=['-s', openocd_scripts] + debug_args.split(),
timeout=5,
encoding='utf-8',
codec_errors='ignore',
env=ocd_env,
)
if self.proc and self.proc.isalive():
self.proc.expect_exact('Info : Listening on port 3333 for gdb connections', timeout=5)
return self
except (pexpect.exceptions.EOF, pexpect.exceptions.TIMEOUT) as e:
logging.error('Error running OpenOCD: %s', str(e))
if self.proc and self.proc.isalive():
self.proc.terminate()
time.sleep(RETRY_DELAY)
logging.error('Failed to run OpenOCD after %d attempts.', MAX_RETRIES)
return None
def connect_telnet(self) -> None:
for attempt in range(1, MAX_RETRIES + 1):
try:
self.telnet = Telnet('127.0.0.1', TELNET_PORT, 5)
break
except ConnectionRefusedError as e:
logging.error('Error telnet connection: %s in attempt:%d', e, attempt)
time.sleep(1)
else:
raise ConnectionRefusedError
def write(self, s: str) -> Any:
if self.telnet is None:
logging.error('Telnet connection is not established.')
return ''
resp = self.telnet.read_very_eager()
self.telnet.write(to_bytes(s, '\n'))
resp += self.telnet.read_until(b'>')
return to_str(resp)
def apptrace_wait_stop(self, timeout: int = 30) -> None:
stopped = False
end_before = time.time() + timeout
while not stopped:
cmd_out = self.write('esp apptrace status')
for line in cmd_out.splitlines():
if line.startswith('Tracing is STOPPED.'):
stopped = True
break
if not stopped and time.time() > end_before:
raise pexpect.TIMEOUT('Failed to wait for apptrace stop!')
time.sleep(1)
def kill(self) -> None:
# Check if the process is still running
if self.proc and self.proc.isalive():
self.proc.terminate()
self.proc.kill(signal.SIGKILL)
def _test_examples_app_trace_basic(dut: IdfDut) -> None:
dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5) dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5)
openocd = OpenOCD(dut).run() with openocd_dut.run() as openocd:
assert openocd
try:
openocd.connect_telnet()
openocd.write('log_output {}'.format(openocd.log_file))
openocd.write('reset run') openocd.write('reset run')
dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5) dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5)
time.sleep(1) # wait for APPTRACE_INIT semihosting call time.sleep(1) # wait for APPTRACE_INIT semihosting call
@@ -150,16 +43,14 @@ def _test_examples_app_trace_basic(dut: IdfDut) -> None:
break break
if found is not True: if found is not True:
raise RuntimeError('"{}" could not be found in {}'.format(log_str, 'apptrace.log')) raise RuntimeError('"{}" could not be found in {}'.format(log_str, 'apptrace.log'))
finally:
openocd.kill()
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.esp32c2
@pytest.mark.esp32s2 @pytest.mark.esp32s2
@pytest.mark.esp32c2
@pytest.mark.jtag @pytest.mark.jtag
def test_examples_app_trace_basic(dut: IdfDut) -> None: def test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_examples_app_trace_basic(dut) _test_examples_app_trace_basic(openocd_dut, dut)
@pytest.mark.esp32s3 @pytest.mark.esp32s3
@@ -167,5 +58,5 @@ def test_examples_app_trace_basic(dut: IdfDut) -> None:
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.esp32h2 @pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag @pytest.mark.usb_serial_jtag
def test_examples_app_trace_basic_usj(dut: IdfDut) -> None: def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_examples_app_trace_basic(dut) _test_examples_app_trace_basic(openocd_dut, dut)

View File

@@ -1,128 +1,22 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os.path import os.path
import signal
import time import time
from telnetlib import Telnet import typing
from typing import Any
from typing import Optional
import pexpect
import pytest import pytest
from pytest_embedded.utils import to_bytes
from pytest_embedded.utils import to_str
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
MAX_RETRIES = 3 if typing.TYPE_CHECKING:
RETRY_DELAY = 1 from conftest import OpenOCD
TELNET_PORT = 4444
class OpenOCD: def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
def __init__(self, dut: 'IdfDut'):
self.dut = dut
self.telnet: Optional[Telnet] = None
self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
self.proc: Optional[pexpect.spawn] = None
def run(self) -> Optional['OpenOCD']:
desc_path = os.path.join(self.dut.app.binary_path, 'project_description.json')
try:
with open(desc_path, 'r') as f:
project_desc = json.load(f)
except FileNotFoundError:
logging.error('Project description file not found at %s', desc_path)
return None
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
logging.error('OPENOCD_SCRIPTS environment variable is not set.')
return None
debug_args = project_desc.get('debug_arguments_openocd')
if not debug_args:
logging.error("'debug_arguments_openocd' key is missing in project_description.json")
return None
# For debug purposes, make the value '4'
ocd_env = os.environ.copy()
ocd_env['LIBUSB_DEBUG'] = '1'
for _ in range(1, MAX_RETRIES + 1):
try:
self.proc = pexpect.spawn(
command='openocd',
args=['-s', openocd_scripts] + debug_args.split(),
timeout=5,
encoding='utf-8',
codec_errors='ignore',
env=ocd_env,
)
if self.proc and self.proc.isalive():
self.proc.expect_exact('Info : Listening on port 3333 for gdb connections', timeout=5)
return self
except (pexpect.exceptions.EOF, pexpect.exceptions.TIMEOUT) as e:
logging.error('Error running OpenOCD: %s', str(e))
if self.proc and self.proc.isalive():
self.proc.terminate()
time.sleep(RETRY_DELAY)
logging.error('Failed to run OpenOCD after %d attempts.', MAX_RETRIES)
return None
def connect_telnet(self) -> None:
for attempt in range(1, MAX_RETRIES + 1):
try:
self.telnet = Telnet('127.0.0.1', TELNET_PORT, 5)
break
except ConnectionRefusedError as e:
logging.error('Error telnet connection: %s in attempt:%d', e, attempt)
time.sleep(1)
else:
raise ConnectionRefusedError
def write(self, s: str) -> Any:
if self.telnet is None:
logging.error('Telnet connection is not established.')
return ''
resp = self.telnet.read_very_eager()
self.telnet.write(to_bytes(s, '\n'))
resp += self.telnet.read_until(b'>')
return to_str(resp)
def apptrace_wait_stop(self, timeout: int = 30) -> None:
stopped = False
end_before = time.time() + timeout
while not stopped:
cmd_out = self.write('esp apptrace status')
for line in cmd_out.splitlines():
if line.startswith('Tracing is STOPPED.'):
stopped = True
break
if not stopped and time.time() > end_before:
raise pexpect.TIMEOUT('Failed to wait for apptrace stop!')
time.sleep(1)
def kill(self) -> None:
# Check if the process is still running
if self.proc and self.proc.isalive():
self.proc.terminate()
self.proc.kill(signal.SIGKILL)
def _test_gcov(dut: IdfDut) -> None:
# create the generated .gcda folder, otherwise would have error: failed to open file. # create the generated .gcda folder, otherwise would have error: failed to open file.
# normally this folder would be created via `idf.py build`. but in CI the non-related files would not be preserved # normally this folder would be created via `idf.py build`. but in CI the non-related files would not be preserved
os.makedirs(os.path.join(dut.app.binary_path, 'esp-idf', 'main', 'CMakeFiles', '__idf_main.dir'), exist_ok=True) os.makedirs(os.path.join(dut.app.binary_path, 'esp-idf', 'main', 'CMakeFiles', '__idf_main.dir'), exist_ok=True)
os.makedirs(os.path.join(dut.app.binary_path, 'esp-idf', 'sample', 'CMakeFiles', '__idf_sample.dir'), exist_ok=True) os.makedirs(os.path.join(dut.app.binary_path, 'esp-idf', 'sample', 'CMakeFiles', '__idf_sample.dir'), exist_ok=True)
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
openocd = OpenOCD(dut).run()
assert openocd
def expect_counter_output(loop: int, timeout: int = 10) -> None: def expect_counter_output(loop: int, timeout: int = 10) -> None:
dut.expect_exact( dut.expect_exact(
[f'blink_dummy_func: Counter = {loop}', f'some_dummy_func: Counter = {loop * 2}'], [f'blink_dummy_func: Counter = {loop}', f'some_dummy_func: Counter = {loop * 2}'],
@@ -152,9 +46,8 @@ def _test_gcov(dut: IdfDut) -> None:
assert len(expect_lines) == 0 assert len(expect_lines) == 0
try: dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
openocd.connect_telnet() with openocd_dut.run() as openocd:
openocd.write('log_output {}'.format(openocd.log_file))
openocd.write('reset run') openocd.write('reset run')
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
@@ -176,21 +69,20 @@ def _test_gcov(dut: IdfDut) -> None:
time.sleep(1) time.sleep(1)
# Test instant run-time dump # Test instant run-time dump
dump_coverage('esp gcov') dump_coverage('esp gcov')
finally:
openocd.kill()
@pytest.mark.jtag @pytest.mark.jtag
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.esp32c2 @pytest.mark.esp32c2
@pytest.mark.esp32s2 @pytest.mark.esp32s2
def test_gcov(dut: IdfDut) -> None: def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_gcov(dut) _test_gcov(openocd_dut, dut)
@pytest.mark.esp32s3
@pytest.mark.esp32c3 @pytest.mark.esp32c3
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.esp32h2 @pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag @pytest.mark.usb_serial_jtag
def test_gcov_usj(dut: IdfDut) -> None: def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_gcov(dut) _test_gcov(openocd_dut, dut)

View File

@@ -133,11 +133,13 @@ static void example_task(void *p)
void app_main(void) void app_main(void)
{ {
ESP_LOGI(TAG, "Ready for OpenOCD connection");
static example_event_data_t event_data[portNUM_PROCESSORS]; static example_event_data_t event_data[portNUM_PROCESSORS];
#if CONFIG_APPTRACE_SV_ENABLE && CONFIG_USE_CUSTOM_EVENT_ID #if CONFIG_APPTRACE_SV_ENABLE && CONFIG_USE_CUSTOM_EVENT_ID
// Currently OpenOCD does not support requesting module info from target. So do the following... // Currently OpenOCD does not support requesting module info from target. So do the following...
// Wait untill SystemView module receives START command from host, // Wait until SystemView module receives START command from host,
// after that data can be sent to the host using onboard API, // after that data can be sent to the host using onboard API,
// so user module description does not need to be requested by OpenOCD itself. // so user module description does not need to be requested by OpenOCD itself.
while (!SEGGER_SYSVIEW_Started()) { while (!SEGGER_SYSVIEW_Started()) {

View File

@@ -1,47 +1,70 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import os.path
import re import re
import time import time
import typing
import pexpect.fdpexpect import pexpect
import pytest import pytest
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
if typing.TYPE_CHECKING:
from conftest import OpenOCD
def _test_examples_sysview_tracing(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
# Construct trace log paths
trace_log = [
os.path.join(dut.logdir, 'sys_log0.svdat') # pylint: disable=protected-access
]
if not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3':
trace_log.append(os.path.join(dut.logdir, 'sys_log1.svdat')) # pylint: disable=protected-access
trace_files = ' '.join([f'file://{log}' for log in trace_log])
# Prepare gdbinit file
gdb_logfile = os.path.join(dut.logdir, 'gdb.txt')
gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit')
gdbinit = os.path.join(dut.logdir, 'gdbinit')
with open(gdbinit_orig, 'r') as f_r, open(gdbinit, 'w') as f_w:
for line in f_r:
if line.startswith('mon esp sysview start'):
f_w.write(f'mon esp sysview start {trace_files}\n')
else:
f_w.write(line)
@pytest.mark.esp32
@pytest.mark.jtag
@pytest.mark.parametrize(
'embedded_services',
[
'esp,idf,jtag',
],
indirect=True,
)
def test_examples_sysview_tracing(dut: IdfDut) -> None:
def dut_expect_task_event() -> None: def dut_expect_task_event() -> None:
dut.expect(re.compile(rb'example: Task\[0x3[0-9A-Fa-f]+\]: received event \d+'), timeout=30) dut.expect(re.compile(rb'example: Task\[0x[0-9A-Fa-f]+\]: received event \d+'), timeout=30)
dut.gdb.write('mon reset halt')
dut.gdb.write('maintenance flush register-cache')
dut.gdb.write('b app_main')
dut.gdb.write('commands', non_blocking=True)
dut.gdb.write(
'mon esp sysview start file:///tmp/sysview_example0.svdat file:///tmp/sysview_example1.svdat', non_blocking=True
)
dut.gdb.write('c', non_blocking=True)
dut.gdb.write('end')
dut.gdb.write('c', non_blocking=True)
time.sleep(1) # to avoid EOF file error
with open(dut.gdb._logfile, encoding='utf-8') as fr: # pylint: disable=protected-access
gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno())
gdb_pexpect_proc.expect('Thread 2 "main" hit Breakpoint 1, app_main ()')
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
with openocd_dut.run() as openocd, open(gdb_logfile, 'w') as gdb_log, pexpect.spawn(
f'idf.py -B {dut.app.binary_path} gdb --batch -x {gdbinit}',
timeout=60,
logfile=gdb_log,
encoding='utf-8',
codec_errors='ignore',
) as p:
p.expect_exact('hit Breakpoint 1, app_main ()')
dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect() dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect()
dut_expect_task_event() dut_expect_task_event()
# Do a sleep while sysview samples are captured. # Do a sleep while sysview samples are captured.
time.sleep(3) time.sleep(3)
# GDB isn't responding now to any commands, therefore, the following command is issued to openocd openocd.write('esp sysview stop')
dut.openocd.write('esp sysview stop')
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32c2
@pytest.mark.jtag
def test_examples_sysview_tracing(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_examples_sysview_tracing(openocd_dut, dut)
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.esp32c6
@pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag
def test_examples_sysview_tracing_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_examples_sysview_tracing(openocd_dut, dut)

View File

@@ -1,5 +1,3 @@
# Enable single core mode by default
CONFIG_FREERTOS_UNICORE=y
# 1ms tick period # 1ms tick period
CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_HZ=1000
# Enable application tracing by default # Enable application tracing by default

View File

@@ -72,6 +72,8 @@ static void alloc_task(void *p)
void app_main(void) void app_main(void)
{ {
ESP_LOGI(TAG, "Ready for OpenOCD connection");
const int num_allocers = 3; const int num_allocers = 3;
char task_name[20]; char task_name[20];
// redirect log messages to the host using SystemView tracing module // redirect log messages to the host using SystemView tracing module

View File

@@ -1,47 +1,46 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import os.path import os.path
import time import typing
import pexpect.fdpexpect import pexpect.fdpexpect
import pytest import pytest
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
if typing.TYPE_CHECKING:
from conftest import OpenOCD
@pytest.mark.esp32
@pytest.mark.jtag def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
@pytest.mark.parametrize('embedded_services', ['esp,idf,jtag'], indirect=True)
def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
# Construct trace log paths # Construct trace log paths
trace_log = [ trace_log = [
os.path.join(os.path.dirname(dut.gdb._logfile), 'heap_log0.svdat') # pylint: disable=protected-access os.path.join(dut.logdir, 'heap_log0.svdat') # pylint: disable=protected-access
] ]
if dut.target in ['esp32', 'esp32s3', 'esp32p4']: if not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3':
trace_log.append(os.path.join(os.path.dirname(dut.gdb._logfile), 'heap_log1.svdat')) # pylint: disable=protected-access trace_log.append(os.path.join(dut.logdir, 'heap_log1.svdat')) # pylint: disable=protected-access
# Set up GDB
dut.gdb.write('set width unlimited') # Don't split output lines for easy parsing
dut.gdb.write('mon reset halt')
dut.gdb.write('maintenance flush register-cache')
# Start sysview tracing
dut.gdb.write('tb heap_trace_start')
dut.gdb.write('commands', non_blocking=True)
trace_files = ' '.join([f'file://{log}' for log in trace_log]) trace_files = ' '.join([f'file://{log}' for log in trace_log])
dut.gdb.write(f'mon esp sysview start {trace_files}', non_blocking=True)
dut.gdb.write('c', non_blocking=True)
dut.gdb.write('end')
# Stop sysview tracing # Prepare gdbinit file
dut.gdb.write('tb heap_trace_stop') gdb_logfile = os.path.join(dut.logdir, 'gdb.txt')
dut.gdb.write('commands', non_blocking=True) gdbinit_orig = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'gdbinit')
dut.gdb.write('mon esp sysview stop', non_blocking=True) gdbinit = os.path.join(dut.logdir, 'gdbinit')
dut.gdb.write('end') with open(gdbinit_orig, 'r') as f_r, open(gdbinit, 'w') as f_w:
dut.gdb.write('c', non_blocking=True) for line in f_r:
if line.startswith('mon esp sysview start'):
f_w.write(f'mon esp sysview start {trace_files}\n')
else:
f_w.write(line)
# Wait for sysview files to be generated dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
time.sleep(1) with openocd_dut.run(), open(gdb_logfile, 'w') as gdb_log, pexpect.spawn(
f'idf.py -B {dut.app.binary_path} gdb --batch -x {gdbinit}',
timeout=60,
logfile=gdb_log,
encoding='utf-8',
codec_errors='ignore',
) as p:
# Wait for sysview files to be generated
p.expect_exact('Tracing is STOPPED')
# Process sysview trace logs # Process sysview trace logs
command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p'] + trace_log command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p'] + trace_log
@@ -49,8 +48,27 @@ def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
sysviewtrace.expect(r'Found \d+ leaked bytes in \d+ blocks.', timeout=120) sysviewtrace.expect(r'Found \d+ leaked bytes in \d+ blocks.', timeout=120)
# Validate GDB logs # Validate GDB logs
with open(dut.gdb._logfile, encoding='utf-8') as fr: # pylint: disable=protected-access with open(gdb_logfile, encoding='utf-8') as fr: # pylint: disable=protected-access
gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno()) gdb_pexpect_proc = pexpect.fdpexpect.fdspawn(fr.fileno())
gdb_pexpect_proc.expect_exact( gdb_pexpect_proc.expect_exact(
'Thread 2 "main" hit Temporary breakpoint 1, heap_trace_start (mode_param', timeout=10) # should be (mode_param=HEAP_TRACE_ALL) # TODO GCC-329 'Thread 2 "main" hit Temporary breakpoint 1, heap_trace_start (mode_param', timeout=10) # should be (mode_param=HEAP_TRACE_ALL) # TODO GCC-329
gdb_pexpect_proc.expect_exact('Thread 2 "main" hit Temporary breakpoint 2, heap_trace_stop ()', timeout=10) gdb_pexpect_proc.expect_exact('Thread 2 "main" hit Temporary breakpoint 2, heap_trace_stop ()', timeout=10)
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
@pytest.mark.jtag
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32c2
def test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
_test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut)
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
@pytest.mark.usb_serial_jtag
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.esp32c6
@pytest.mark.esp32h2
def test_examples_sysview_tracing_heap_log_usj(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
_test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut)

View File

@@ -42,6 +42,11 @@
"rev": 3, "rev": 3,
"build_date_str_addr": "0x3ff1a374", "build_date_str_addr": "0x3ff1a374",
"build_date_str": "Feb 7 2021" "build_date_str": "Feb 7 2021"
},
{
"rev": 101,
"build_date_str_addr": "0x3ff1a3dc",
"build_date_str": "Mar 1 2023"
} }
], ],
"esp32c6": [ "esp32c6": [

View File

@@ -126,9 +126,9 @@ tools/test_apps/system/g1_components:
tools/test_apps/system/gdb: tools/test_apps/system/gdb:
disable_test: disable_test:
- if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c61", "esp32p4"] - if: IDF_TARGET == "esp32p4"
temporary: true temporary: true
reason: lack of runners reason: not supported yet # TODO: IDF-13142
tools/test_apps/system/gdb_loadable_elf: tools/test_apps/system/gdb_loadable_elf:
disable_test: disable_test:

View File

@@ -1,73 +1,18 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os import os
import re import re
import subprocess import typing
import time
from subprocess import Popen
from typing import Optional
import pexpect import pexpect
import pytest import pytest
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
if typing.TYPE_CHECKING:
MAX_RETRIES = 3 from conftest import OpenOCD
RETRY_DELAY = 3 # seconds
def run_openocd(dut: IdfDut) -> Optional[Popen]: def _test_idf_gdb(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
desc_path = os.path.join(dut.app.binary_path, 'project_description.json')
try:
with open(desc_path, 'r') as f:
project_desc = json.load(f)
except FileNotFoundError:
logging.error('Project description file not found at %s', desc_path)
return None
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
logging.error('OPENOCD_SCRIPTS environment variable is not set.')
return None
debug_args = project_desc.get('debug_arguments_openocd')
if not debug_args:
logging.error("'debug_arguments_openocd' key is missing in project_description.json")
return None
cmd = ['openocd'] + ['-s', openocd_scripts] + debug_args.split()
# For debug purpose, make the value '4'
ocd_env = os.environ.copy()
ocd_env['LIBUSB_DEBUG'] = '1'
for attempt in range(1, MAX_RETRIES + 1):
logging.info('Attempt %d: Running %s', attempt, cmd)
with open(os.path.join(dut.logdir, 'ocd.txt'), 'w') as ocd_log:
try:
ocd = subprocess.Popen(cmd, stdout=ocd_log, stderr=ocd_log, env=ocd_env)
time.sleep(1)
# Check if the process is running successfully
if ocd.poll() is None:
return ocd
else:
logging.error('OpenOCD exited with error code %d', ocd.returncode)
except subprocess.SubprocessError as e:
logging.error('Error running OpenOCD: %s', e)
logging.warning("OpenOCD couldn't be run. Retrying in %d seconds...", RETRY_DELAY)
time.sleep(RETRY_DELAY)
logging.error('Failed to run OpenOCD after %d attempts.', MAX_RETRIES)
return None
def _test_idf_gdb(dut: IdfDut) -> None:
# Need to wait a moment to connect via OpenOCD after the hard reset happened. # Need to wait a moment to connect via OpenOCD after the hard reset happened.
# Along with this check that app runs ok # Along with this check that app runs ok
dut.expect('Hello world!') dut.expect('Hello world!')
@@ -75,35 +20,32 @@ def _test_idf_gdb(dut: IdfDut) -> None:
# Don't need to have output from UART anymore # Don't need to have output from UART anymore
dut.serial.stop_redirect_thread() dut.serial.stop_redirect_thread()
ocd = run_openocd(dut) with openocd_dut.run(), open(os.path.join(dut.logdir, 'gdb.txt'), 'w') as gdb_log, pexpect.spawn(
assert ocd f'idf.py -B {dut.app.binary_path} gdb --batch',
timeout=60,
try: logfile=gdb_log,
with open(os.path.join(dut.logdir, 'gdb.txt'), 'w') as gdb_log, \ encoding='utf-8',
pexpect.spawn(f'idf.py -B {dut.app.binary_path} gdb --batch', codec_errors='ignore',
timeout=60, ) as p:
logfile=gdb_log, p.expect(re.compile(r'add symbol table from file.*bootloader.elf'))
encoding='utf-8', p.expect(
codec_errors='ignore') as p: re.compile(r'add symbol table from file.*rom.elf')
p.expect(re.compile(r'add symbol table from file.*bootloader.elf')) ) # if fail here: add target support here https://github.com/espressif/esp-rom-elfs
p.expect(re.compile(r'add symbol table from file.*rom.elf')) # if fail here: add target support here https://github.com/espressif/esp-rom-elfs p.expect_exact('hit Temporary breakpoint 1, app_main ()')
p.expect_exact('hit Temporary breakpoint 1, app_main ()')
finally:
# Check if the process is still running
if ocd.poll() is None:
ocd.terminate()
ocd.kill()
@pytest.mark.jtag
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.esp32s2 @pytest.mark.esp32s2
@pytest.mark.jtag @pytest.mark.esp32c2
def test_idf_gdb(dut: IdfDut) -> None: def test_idf_gdb(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_idf_gdb(dut) _test_idf_gdb(openocd_dut, dut)
@pytest.mark.usb_serial_jtag
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.esp32h2 @pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag def test_idf_gdb_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
def test_idf_gdb_usj(dut: IdfDut) -> None: _test_idf_gdb(openocd_dut, dut)
_test_idf_gdb(dut)