From 66defc0c1983e0101c24c1f1a5e9887b3d7819fe Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Mon, 28 Apr 2025 15:58:35 +0200 Subject: [PATCH 1/8] ci: enable sysview examples for all chips --- examples/system/.build-test-rules.yml | 19 +- examples/system/gcov/pytest_gcov.py | 4 +- .../sysview_tracing/main/sysview_tracing.c | 2 + .../sysview_tracing/pytest_sysview_tracing.py | 195 +++++++++++++++--- .../system/sysview_tracing/sdkconfig.defaults | 2 - .../main/sysview_heap_log.c | 2 + .../pytest_sysview_tracing_heap_log.py | 183 +++++++++++++--- 7 files changed, 336 insertions(+), 71 deletions(-) diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 0132564e9e..3ad52e862c 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -94,9 +94,6 @@ examples/system/gcov: - if: IDF_TARGET == "esp32p4" temporary: true reason: lack of runners - - if: IDF_TARGET == "esp32s3" - temporary: true - reason: unstable, known data corruption issue #TODO: OCD-1048 - if: IDF_TARGET == "esp32h21" temporary: true reason: not supported yet #TODO: OCD-1079 @@ -265,17 +262,29 @@ examples/system/sysview_tracing: disable: - if: SOC_GPTIMER_SUPPORTED != 1 disable_test: - - if: IDF_TARGET != "esp32" + - if: IDF_TARGET == "esp32p4" temporary: true reason: lack of runners + - if: IDF_TARGET == "esp32h21" + temporary: true + reason: not supported yet #TODO: OCD-1082 + - if: IDF_TARGET == "esp32h4" + temporary: true + reason: not supported yet #TODO: OCD-1136 examples/system/sysview_tracing_heap_log: disable: - if: SOC_GPTIMER_SUPPORTED != 1 disable_test: - - if: IDF_TARGET != "esp32" + - if: IDF_TARGET == "esp32p4" temporary: true reason: lack of runners + - if: IDF_TARGET == "esp32h21" + temporary: true + reason: not supported yet #TODO: OCD-1082 + - if: IDF_TARGET == "esp32h4" + temporary: true + reason: not supported yet #TODO: OCD-1136 examples/system/task_watchdog: disable: diff --git a/examples/system/gcov/pytest_gcov.py b/examples/system/gcov/pytest_gcov.py index 59c4763579..1558eb3707 100644 --- a/examples/system/gcov/pytest_gcov.py +++ b/examples/system/gcov/pytest_gcov.py @@ -188,6 +188,8 @@ def test_gcov(dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag -@idf_parametrize('target', ['esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'] +) def test_gcov_usj(dut: IdfDut) -> None: _test_gcov(dut) diff --git a/examples/system/sysview_tracing/main/sysview_tracing.c b/examples/system/sysview_tracing/main/sysview_tracing.c index 4cf8799549..115224fb97 100644 --- a/examples/system/sysview_tracing/main/sysview_tracing.c +++ b/examples/system/sysview_tracing/main/sysview_tracing.c @@ -134,6 +134,8 @@ static void example_task(void *p) void app_main(void) { + ESP_LOGI(TAG, "Ready for OpenOCD connection"); + static example_event_data_t event_data[CONFIG_FREERTOS_NUMBER_OF_CORES]; #if CONFIG_APPTRACE_SV_ENABLE && CONFIG_USE_CUSTOM_EVENT_ID diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index 570d941039..015e030d55 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -1,48 +1,175 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 +import json +import logging +import os.path import re +import signal import time +from telnetlib import Telnet +from typing import Any +from typing import Optional -import pexpect.fdpexpect +import pexpect 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.utils import idf_parametrize +MAX_RETRIES = 3 +RETRY_DELAY = 1 +TELNET_PORT = 4444 + + +class OpenOCD: + 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_sysview_tracing(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) + + def dut_expect_task_event() -> None: + dut.expect(re.compile(rb'example: Task\[0x[0-9A-Fa-f]+\]: received event \d+'), timeout=30) + + dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) + openocd = OpenOCD(dut).run() + assert openocd + try: + openocd.connect_telnet() + openocd.write('log_output {}'.format(openocd.log_file)) + + with 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_task_event() + + # Do a sleep while sysview samples are captured. + time.sleep(3) + openocd.write('esp sysview stop') + finally: + openocd.kill() + @pytest.mark.jtag -@pytest.mark.parametrize( - 'embedded_services', - [ - 'esp,idf,jtag', - ], - indirect=True, -) -@idf_parametrize('target', ['esp32'], indirect=['target']) +@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) def test_examples_sysview_tracing(dut: IdfDut) -> None: - def dut_expect_task_event() -> None: - dut.expect(re.compile(rb'example: Task\[0x3[0-9A-Fa-f]+\]: received event \d+'), timeout=30) + _test_examples_sysview_tracing(dut) - 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('example: Created task') # dut has been restarted by gdb since the last dut.expect() - dut_expect_task_event() - - # Do a sleep while sysview samples are captured. - time.sleep(3) - # GDB isn't responding now to any commands, therefore, the following command is issued to openocd - dut.openocd.write('esp sysview stop') +@pytest.mark.usb_serial_jtag +@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target']) +def test_examples_sysview_tracing_usj(dut: IdfDut) -> None: + _test_examples_sysview_tracing(dut) diff --git a/examples/system/sysview_tracing/sdkconfig.defaults b/examples/system/sysview_tracing/sdkconfig.defaults index d93b1caeb7..a08cc7e128 100644 --- a/examples/system/sysview_tracing/sdkconfig.defaults +++ b/examples/system/sysview_tracing/sdkconfig.defaults @@ -1,5 +1,3 @@ -# Enable single core mode by default -CONFIG_FREERTOS_UNICORE=y # 1ms tick period CONFIG_FREERTOS_HZ=1000 # Enable application tracing by default diff --git a/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c b/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c index 87b0031d07..aecf041da5 100644 --- a/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c +++ b/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c @@ -73,6 +73,8 @@ static void alloc_task(void *p) void app_main(void) { + ESP_LOGI(TAG, "Ready for OpenOCD connection"); + const int num_allocers = 3; char task_name[20]; // redirect log messages to the host using SystemView tracing module diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index 79871369d5..f8b56ec69f 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -1,48 +1,157 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 +import json +import logging import os.path +import signal import time +from telnetlib import Telnet +from typing import Any +from typing import Optional import pexpect.fdpexpect 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.utils import idf_parametrize +MAX_RETRIES = 3 +RETRY_DELAY = 1 +TELNET_PORT = 4444 -@pytest.mark.jtag -@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True) -@pytest.mark.parametrize('embedded_services', ['esp,idf,jtag'], indirect=True) -@idf_parametrize('target', ['esp32'], indirect=['target']) -def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: + +class OpenOCD: + 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_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: # Construct trace log paths 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']: - trace_log.append(os.path.join(os.path.dirname(dut.gdb._logfile), '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) + if not dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') or dut.target == 'esp32s3': + trace_log.append(os.path.join(dut.logdir, 'heap_log1.svdat')) # pylint: disable=protected-access 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 - dut.gdb.write('tb heap_trace_stop') - dut.gdb.write('commands', non_blocking=True) - dut.gdb.write('mon esp sysview stop', non_blocking=True) - dut.gdb.write('end') - dut.gdb.write('c', non_blocking=True) + # 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) - # Wait for sysview files to be generated - time.sleep(1) + dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) + openocd = OpenOCD(dut).run() + assert openocd + try: + openocd.connect_telnet() + openocd.write('log_output {}'.format(openocd.log_file)) + + with 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') + finally: + openocd.kill() # Process sysview trace logs command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p'] + trace_log @@ -50,9 +159,25 @@ 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) # 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.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 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 +@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) +def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: + _test_examples_sysview_tracing_heap_log(idf_path, dut) + + +@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True) +@pytest.mark.usb_serial_jtag +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'] +) +def test_examples_sysview_tracing_heap_log_usj(idf_path: str, dut: IdfDut) -> None: + _test_examples_sysview_tracing_heap_log(idf_path, dut) From ee65205183691cb4a07442b6e09a09f3f1c094c4 Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Tue, 20 May 2025 10:44:33 +0200 Subject: [PATCH 2/8] ci: OpenOCD class as fixture --- conftest.py | 114 +++++++++++++ .../app_trace_basic/pytest_app_trace_basic.py | 127 ++------------- examples/system/gcov/pytest_gcov.py | 129 ++------------- .../sysview_tracing/pytest_sysview_tracing.py | 151 +++--------------- .../pytest_sysview_tracing_heap_log.py | 144 ++--------------- 5 files changed, 171 insertions(+), 494 deletions(-) diff --git a/conftest.py b/conftest.py index af4a11161c..dbb90ba876 100644 --- a/conftest.py +++ b/conftest.py @@ -18,15 +18,20 @@ if os.path.join(os.path.dirname(__file__), 'tools', 'ci') not in sys.path: if os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages') not in sys.path: sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages')) +import json import logging import os import re +import signal +import time import typing as t from copy import deepcopy +from telnetlib import Telnet from urllib.parse import quote import common_test_methods # noqa: F401 import gitlab_api +import pexpect import pytest from _pytest.config import Config from _pytest.fixtures import FixtureRequest @@ -39,6 +44,8 @@ from idf_pytest.plugin import IdfLocalPlugin from idf_pytest.plugin import requires_elf_or_map from idf_pytest.utils import format_case_id 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.unity_tester import CaseTester @@ -142,6 +149,113 @@ class AppDownloader: PRESIGNED_JSON = 'presigned.json' +class OpenOCD: + def __init__(self, dut: 'IdfDut'): + self.MAX_RETRIES = 3 + self.RETRY_DELAY = 1 + self.TELNET_PORT = 4444 + self.dut = dut + self.telnet: t.Optional[Telnet] = None + self.log_file = os.path.join(self.dut.logdir, 'ocd.txt') + self.proc: t.Optional[pexpect.spawn] = None + + def __enter__(self) -> 'OpenOCD': + return self + + def __exit__(self, exception_type: t.Any, exception_value: t.Any, exception_traceback: t.Any) -> None: + self.kill() + + def run(self) -> t.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) -> t.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(scope='session') def app_downloader( pipeline_id: t.Optional[str], diff --git a/examples/system/app_trace_basic/pytest_app_trace_basic.py b/examples/system/app_trace_basic/pytest_app_trace_basic.py index 004e07632b..bfa4eb98d1 100644 --- a/examples/system/app_trace_basic/pytest_app_trace_basic.py +++ b/examples/system/app_trace_basic/pytest_app_trace_basic.py @@ -1,126 +1,19 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 -import json -import logging -import os -import signal import time -from telnetlib import Telnet -from typing import Any -from typing import Optional +import typing -import pexpect 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.utils import idf_parametrize -MAX_RETRIES = 3 -RETRY_DELAY = 1 -TELNET_PORT = 4444 +if typing.TYPE_CHECKING: + from conftest import OpenOCD -class OpenOCD: - 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: +def _test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5) - openocd = OpenOCD(dut).run() - assert openocd - try: - openocd.connect_telnet() - openocd.write('log_output {}'.format(openocd.log_file)) + with openocd_dut.run() as openocd: openocd.write('reset run') dut.expect_exact('example: Waiting for OpenOCD connection', timeout=5) time.sleep(1) # wait for APPTRACE_INIT semihosting call @@ -151,17 +44,15 @@ def _test_examples_app_trace_basic(dut: IdfDut) -> None: break if found is not True: raise RuntimeError('"{}" could not be found in {}'.format(log_str, 'apptrace.log')) - finally: - openocd.kill() @pytest.mark.jtag @idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) -def test_examples_app_trace_basic(dut: IdfDut) -> None: - _test_examples_app_trace_basic(dut) +def test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_examples_app_trace_basic(openocd_dut, dut) @pytest.mark.usb_serial_jtag @idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target']) -def test_examples_app_trace_basic_usj(dut: IdfDut) -> None: - _test_examples_app_trace_basic(dut) +def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_examples_app_trace_basic(openocd_dut, dut) diff --git a/examples/system/gcov/pytest_gcov.py b/examples/system/gcov/pytest_gcov.py index 1558eb3707..440dcf680a 100644 --- a/examples/system/gcov/pytest_gcov.py +++ b/examples/system/gcov/pytest_gcov.py @@ -1,129 +1,23 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 -import json -import logging import os.path -import signal import time -from telnetlib import Telnet -from typing import Any -from typing import Optional +import typing -import pexpect 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.utils import idf_parametrize -MAX_RETRIES = 3 -RETRY_DELAY = 1 -TELNET_PORT = 4444 +if typing.TYPE_CHECKING: + from conftest import OpenOCD -class OpenOCD: - 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: +def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: # 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 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) - 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: dut.expect_exact( [f'blink_dummy_func: Counter = {loop}', f'some_dummy_func: Counter = {loop * 2}'], @@ -153,9 +47,8 @@ def _test_gcov(dut: IdfDut) -> None: assert len(expect_lines) == 0 - try: - openocd.connect_telnet() - openocd.write('log_output {}'.format(openocd.log_file)) + dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) + with openocd_dut.run() as openocd: openocd.write('reset run') dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) @@ -177,19 +70,17 @@ def _test_gcov(dut: IdfDut) -> None: time.sleep(1) # Test instant run-time dump dump_coverage('esp gcov') - finally: - openocd.kill() @pytest.mark.jtag @idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) -def test_gcov(dut: IdfDut) -> None: - _test_gcov(dut) +def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_gcov(openocd_dut, dut) @pytest.mark.usb_serial_jtag @idf_parametrize( 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'] ) -def test_gcov_usj(dut: IdfDut) -> None: - _test_gcov(dut) +def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_gcov(openocd_dut, dut) diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index 015e030d55..f502fd27d7 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -1,121 +1,20 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 -import json -import logging import os.path import re -import signal import time -from telnetlib import Telnet -from typing import Any -from typing import Optional +import typing import pexpect 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.utils import idf_parametrize -MAX_RETRIES = 3 -RETRY_DELAY = 1 -TELNET_PORT = 4444 +if typing.TYPE_CHECKING: + from conftest import OpenOCD -class OpenOCD: - 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_sysview_tracing(dut: IdfDut) -> None: +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 @@ -139,37 +38,29 @@ def _test_examples_sysview_tracing(dut: IdfDut) -> None: dut.expect(re.compile(rb'example: Task\[0x[0-9A-Fa-f]+\]: received event \d+'), timeout=30) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) - openocd = OpenOCD(dut).run() - assert openocd - try: - openocd.connect_telnet() - openocd.write('log_output {}'.format(openocd.log_file)) + 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_task_event() - with 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_task_event() - - # Do a sleep while sysview samples are captured. - time.sleep(3) - openocd.write('esp sysview stop') - finally: - openocd.kill() + # Do a sleep while sysview samples are captured. + time.sleep(3) + openocd.write('esp sysview stop') @pytest.mark.jtag @idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) -def test_examples_sysview_tracing(dut: IdfDut) -> None: - _test_examples_sysview_tracing(dut) +def test_examples_sysview_tracing(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_examples_sysview_tracing(openocd_dut, dut) @pytest.mark.usb_serial_jtag @idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target']) -def test_examples_sysview_tracing_usj(dut: IdfDut) -> None: - _test_examples_sysview_tracing(dut) +def test_examples_sysview_tracing_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_examples_sysview_tracing(openocd_dut, dut) diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index f8b56ec69f..f37fe64f0f 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -1,120 +1,18 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 -import json -import logging import os.path -import signal -import time -from telnetlib import Telnet -from typing import Any -from typing import Optional +import typing import pexpect.fdpexpect 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.utils import idf_parametrize -MAX_RETRIES = 3 -RETRY_DELAY = 1 -TELNET_PORT = 4444 +if typing.TYPE_CHECKING: + from conftest import OpenOCD -class OpenOCD: - 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_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: +def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None: # Construct trace log paths trace_log = [ os.path.join(dut.logdir, 'heap_log0.svdat') # pylint: disable=protected-access @@ -135,23 +33,15 @@ def _test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: f_w.write(line) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) - openocd = OpenOCD(dut).run() - assert openocd - try: - openocd.connect_telnet() - openocd.write('log_output {}'.format(openocd.log_file)) - - with 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') - finally: - openocd.kill() + 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 command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p'] + trace_log @@ -170,8 +60,8 @@ def _test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: @pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True) @pytest.mark.jtag @idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) -def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: - _test_examples_sysview_tracing_heap_log(idf_path, dut) +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) @@ -179,5 +69,5 @@ def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None: @idf_parametrize( 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'] ) -def test_examples_sysview_tracing_heap_log_usj(idf_path: str, dut: IdfDut) -> None: - _test_examples_sysview_tracing_heap_log(idf_path, dut) +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) From 7b724fadd30eac06de5ca102bb5d884eb1330f4d Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Tue, 20 May 2025 17:29:33 +0200 Subject: [PATCH 3/8] ci: use shared OpenOCD class for GDB test app --- tools/test_apps/system/.build-test-rules.yml | 5 +- tools/test_apps/system/gdb/pytest_gdb.py | 106 ++++--------------- 2 files changed, 26 insertions(+), 85 deletions(-) diff --git a/tools/test_apps/system/.build-test-rules.yml b/tools/test_apps/system/.build-test-rules.yml index c52bbe3cdd..54df7b5182 100644 --- a/tools/test_apps/system/.build-test-rules.yml +++ b/tools/test_apps/system/.build-test-rules.yml @@ -47,9 +47,12 @@ tools/test_apps/system/g1_components: tools/test_apps/system/gdb: disable_test: - - if: IDF_TARGET in ["esp32s3", "esp32c2", "esp32c3", "esp32c5", "esp32c61", "esp32p4"] + - if: IDF_TARGET == "esp32p4" temporary: true reason: lack of runners + - if: IDF_TARGET in ["esp32c5", "esp32c61"] + temporary: true + reason: not supported yet # TODO: IDF-13142 tools/test_apps/system/gdb_loadable_elf: disable_test: diff --git a/tools/test_apps/system/gdb/pytest_gdb.py b/tools/test_apps/system/gdb/pytest_gdb.py index 18b4ac573c..3e2ffdc97d 100644 --- a/tools/test_apps/system/gdb/pytest_gdb.py +++ b/tools/test_apps/system/gdb/pytest_gdb.py @@ -1,72 +1,19 @@ # SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Unlicense OR CC0-1.0 -import json -import logging import os import re -import subprocess -import time -from subprocess import Popen -from typing import Optional +import typing import pexpect import pytest from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize -MAX_RETRIES = 3 -RETRY_DELAY = 3 # seconds +if typing.TYPE_CHECKING: + from conftest import OpenOCD -def run_openocd(dut: IdfDut) -> Optional[Popen]: - 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: +def _test_idf_gdb(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: # Need to wait a moment to connect via OpenOCD after the hard reset happened. # Along with this check that app runs ok dut.expect('Hello world!') @@ -74,36 +21,27 @@ def _test_idf_gdb(dut: IdfDut) -> None: # Don't need to have output from UART anymore dut.serial.stop_redirect_thread() - ocd = run_openocd(dut) - assert ocd - - try: - with open(os.path.join(dut.logdir, 'gdb.txt'), 'w') as gdb_log, pexpect.spawn( - f'idf.py -B {dut.app.binary_path} gdb --batch', - timeout=60, - logfile=gdb_log, - encoding='utf-8', - codec_errors='ignore', - ) as p: - p.expect(re.compile(r'add symbol table from file.*bootloader.elf')) - 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 ()') - finally: - # Check if the process is still running - if ocd.poll() is None: - ocd.terminate() - ocd.kill() + with openocd_dut.run(), open(os.path.join(dut.logdir, 'gdb.txt'), 'w') as gdb_log, pexpect.spawn( + f'idf.py -B {dut.app.binary_path} gdb --batch', + timeout=60, + logfile=gdb_log, + encoding='utf-8', + codec_errors='ignore', + ) as p: + p.expect(re.compile(r'add symbol table from file.*bootloader.elf')) + 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 ()') @pytest.mark.jtag -@idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target']) -def test_idf_gdb(dut: IdfDut) -> None: - _test_idf_gdb(dut) +@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target']) +def test_idf_gdb(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_idf_gdb(openocd_dut, dut) @pytest.mark.usb_serial_jtag -@idf_parametrize('target', ['esp32c6', 'esp32h2'], indirect=['target']) -def test_idf_gdb_usj(dut: IdfDut) -> None: - _test_idf_gdb(dut) +@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2'], indirect=['target']) +def test_idf_gdb_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: + _test_idf_gdb(openocd_dut, dut) From 55617bb5e76d565738e70ab735d62947e06f580b Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 23 May 2025 09:20:36 +0200 Subject: [PATCH 4/8] feat(tools): add esp32c3 rev1.1 rom version string --- tools/idf_py_actions/roms.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/idf_py_actions/roms.json b/tools/idf_py_actions/roms.json index 01524dc9cf..23209f74eb 100644 --- a/tools/idf_py_actions/roms.json +++ b/tools/idf_py_actions/roms.json @@ -42,6 +42,11 @@ "rev": 3, "build_date_str_addr": "0x3ff1a374", "build_date_str": "Feb 7 2021" + }, + { + "rev": 101, + "build_date_str_addr": "0x3ff1a3dc", + "build_date_str": "Mar 1 2023" } ], "esp32c5": [ From d7eea89e16a98305d8f032929465f67bea5d6f86 Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Tue, 3 Jun 2025 12:55:03 +0200 Subject: [PATCH 5/8] ci: enable example tests for esp32p4 --- examples/system/.build-test-rules.yml | 12 ------------ .../system/app_trace_basic/pytest_app_trace_basic.py | 4 +++- examples/system/gcov/pytest_gcov.py | 2 +- .../system/sysview_tracing/pytest_sysview_tracing.py | 4 +++- .../pytest_sysview_tracing_heap_log.py | 2 +- tools/test_apps/system/.build-test-rules.yml | 5 +---- 6 files changed, 9 insertions(+), 20 deletions(-) diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 3ad52e862c..143e05c10e 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -2,9 +2,6 @@ examples/system/app_trace_basic: disable_test: - - if: IDF_TARGET == "esp32p4" - temporary: true - reason: lack of runners. - if: IDF_TARGET == "esp32h21" temporary: true reason: not supported yet #TODO: OCD-1081 @@ -91,9 +88,6 @@ examples/system/freertos/real_time_stats: examples/system/gcov: disable_test: - - if: IDF_TARGET == "esp32p4" - temporary: true - reason: lack of runners - if: IDF_TARGET == "esp32h21" temporary: true reason: not supported yet #TODO: OCD-1079 @@ -262,9 +256,6 @@ examples/system/sysview_tracing: disable: - if: SOC_GPTIMER_SUPPORTED != 1 disable_test: - - if: IDF_TARGET == "esp32p4" - temporary: true - reason: lack of runners - if: IDF_TARGET == "esp32h21" temporary: true reason: not supported yet #TODO: OCD-1082 @@ -276,9 +267,6 @@ examples/system/sysview_tracing_heap_log: disable: - if: SOC_GPTIMER_SUPPORTED != 1 disable_test: - - if: IDF_TARGET == "esp32p4" - temporary: true - reason: lack of runners - if: IDF_TARGET == "esp32h21" temporary: true reason: not supported yet #TODO: OCD-1082 diff --git a/examples/system/app_trace_basic/pytest_app_trace_basic.py b/examples/system/app_trace_basic/pytest_app_trace_basic.py index bfa4eb98d1..e1f0645236 100644 --- a/examples/system/app_trace_basic/pytest_app_trace_basic.py +++ b/examples/system/app_trace_basic/pytest_app_trace_basic.py @@ -53,6 +53,8 @@ def test_examples_app_trace_basic(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag -@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], indirect=['target'] +) def test_examples_app_trace_basic_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_examples_app_trace_basic(openocd_dut, dut) diff --git a/examples/system/gcov/pytest_gcov.py b/examples/system/gcov/pytest_gcov.py index 440dcf680a..49e4be994e 100644 --- a/examples/system/gcov/pytest_gcov.py +++ b/examples/system/gcov/pytest_gcov.py @@ -80,7 +80,7 @@ def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag @idf_parametrize( - 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'] + 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], indirect=['target'] ) def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_gcov(openocd_dut, dut) diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index f502fd27d7..7733656b72 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -61,6 +61,8 @@ def test_examples_sysview_tracing(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: @pytest.mark.usb_serial_jtag -@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target']) +@idf_parametrize( + 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], indirect=['target'] +) def test_examples_sysview_tracing_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None: _test_examples_sysview_tracing(openocd_dut, dut) diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index f37fe64f0f..c2f4bcd551 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -67,7 +67,7 @@ def test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str @pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True) @pytest.mark.usb_serial_jtag @idf_parametrize( - 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'] + 'target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4'], indirect=['target'] ) 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) diff --git a/tools/test_apps/system/.build-test-rules.yml b/tools/test_apps/system/.build-test-rules.yml index 54df7b5182..9fc3fac138 100644 --- a/tools/test_apps/system/.build-test-rules.yml +++ b/tools/test_apps/system/.build-test-rules.yml @@ -47,10 +47,7 @@ tools/test_apps/system/g1_components: tools/test_apps/system/gdb: disable_test: - - if: IDF_TARGET == "esp32p4" - temporary: true - reason: lack of runners - - if: IDF_TARGET in ["esp32c5", "esp32c61"] + - if: IDF_TARGET in ["esp32p4", "esp32c5", "esp32c61"] temporary: true reason: not supported yet # TODO: IDF-13142 From afe83c15aaa7d6472e4c160a954446b046192e7c Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Thu, 5 Jun 2025 21:17:52 +0200 Subject: [PATCH 6/8] fix(sysview): fix timestamp freq when not using APB clock --- .../esp/SEGGER_SYSVIEW_Config_FreeRTOS.c | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/components/app_trace/sys_view/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c b/components/app_trace/sys_view/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c index 0e7aba7bdf..9761960d75 100644 --- a/components/app_trace/sys_view/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c +++ b/components/app_trace/sys_view/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c @@ -65,6 +65,7 @@ Revision: $Rev: 7745 $ #include "esp_app_trace.h" #include "esp_app_trace_util.h" #include "esp_intr_alloc.h" +#include "esp_clk_tree.h" #include "esp_cpu.h" #include "soc/soc.h" #include "soc/interrupts.h" @@ -101,9 +102,6 @@ extern const SEGGER_SYSVIEW_OS_API SYSVIEW_X_OS_TraceAPI; // Timer group timer divisor #define SYSVIEW_TIMER_DIV 2 -// Frequency of the timestamp, using APB as GPTimer source clock -#define SYSVIEW_TIMESTAMP_FREQ (esp_clk_apb_freq() / SYSVIEW_TIMER_DIV) - // GPTimer handle gptimer_handle_t s_sv_gptimer; @@ -173,30 +171,38 @@ static void _cbSendSystemDesc(void) { * ********************************************************************** */ -static void SEGGER_SYSVIEW_TS_Init(void) +static int SEGGER_SYSVIEW_TS_Init(void) { /* We only need to initialize something if we use Timer Group. * esp_timer and ccount can be used as is. */ #if TS_USE_TIMERGROUP + // get clock source frequency + uint32_t counter_src_hz = 0; + ESP_ERROR_CHECK(esp_clk_tree_src_get_freq_hz( + (soc_module_clk_t)GPTIMER_CLK_SRC_DEFAULT, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &counter_src_hz)); gptimer_config_t config = { .clk_src = GPTIMER_CLK_SRC_DEFAULT, .direction = GPTIMER_COUNT_UP, - .resolution_hz = SYSVIEW_TIMESTAMP_FREQ, + .resolution_hz = counter_src_hz / SYSVIEW_TIMER_DIV, }; // pick any free GPTimer instance ESP_ERROR_CHECK(gptimer_new_timer(&config, &s_sv_gptimer)); /* Start counting */ gptimer_enable(s_sv_gptimer); gptimer_start(s_sv_gptimer); + return config.resolution_hz; +#else + return SYSVIEW_TIMESTAMP_FREQ; #endif // TS_USE_TIMERGROUP } void SEGGER_SYSVIEW_Conf(void) { U32 disable_evts = 0; - SEGGER_SYSVIEW_TS_Init(); - SEGGER_SYSVIEW_Init(SYSVIEW_TIMESTAMP_FREQ, SYSVIEW_CPU_FREQ, + int timestamp_freq = SEGGER_SYSVIEW_TS_Init(); + SEGGER_SYSVIEW_Init(timestamp_freq, SYSVIEW_CPU_FREQ, &SYSVIEW_X_OS_TraceAPI, _cbSendSystemDesc); SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE); From a678721e49eb06e99ef38c82db0ea8502acfa949 Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Tue, 8 Jul 2025 16:01:23 +0200 Subject: [PATCH 7/8] ci: enable c61 usb_serial_jtag tests --- .../templates/known_generate_test_child_pipeline_warnings.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/ci/dynamic_pipelines/templates/known_generate_test_child_pipeline_warnings.yml b/tools/ci/dynamic_pipelines/templates/known_generate_test_child_pipeline_warnings.yml index 71fa9250eb..05efc0b22f 100644 --- a/tools/ci/dynamic_pipelines/templates/known_generate_test_child_pipeline_warnings.yml +++ b/tools/ci/dynamic_pipelines/templates/known_generate_test_child_pipeline_warnings.yml @@ -23,7 +23,6 @@ no_runner_tags: - esp32c5,jtag - esp32c6,jtag - esp32c61,jtag - - esp32c61,usb_serial_jtag - esp32h2,jtag - esp32p4,jtag - esp32s2,usb_host_flash_disk From 824d54e707315df7e1da924c29c8fbf93ccfd6be Mon Sep 17 00:00:00 2001 From: Samuel Obuch Date: Wed, 9 Jul 2025 11:56:36 +0200 Subject: [PATCH 8/8] ci: fix appimage_offset for esp32p4 in sysview heap trace example --- .../pytest_sysview_tracing_heap_log.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py index c2f4bcd551..2b201d131d 100644 --- a/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py +++ b/examples/system/sysview_tracing_heap_log/pytest_sysview_tracing_heap_log.py @@ -33,15 +33,18 @@ def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: st f_w.write(line) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) - 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') + with openocd_dut.run() as oocd: + if dut.target == 'esp32p4': + oocd.write('esp appimage_offset 0x20000') + with 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 command = [os.path.join(idf_path, 'tools', 'esp_app_trace', 'sysviewtrace_proc.py'), '-p'] + trace_log