mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-30 18:57:19 +02:00
Merge branch 'ci/enable_sysview_tests' into 'master'
ci: enable sysview example tests for all chips Closes IDFCI-807, IDFCI-808, and IDF-13000 See merge request espressif/esp-idf!39153
This commit is contained in:
114
conftest.py
114
conftest.py
@ -19,16 +19,21 @@ if os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages') not
|
|||||||
|
|
||||||
import glob
|
import glob
|
||||||
import io
|
import io
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
import typing as t
|
import typing as t
|
||||||
import zipfile
|
import zipfile
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from telnetlib import Telnet
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
import common_test_methods # noqa: F401
|
import common_test_methods # noqa: F401
|
||||||
import gitlab_api
|
import gitlab_api
|
||||||
|
import pexpect
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
@ -53,6 +58,8 @@ from idf_pytest.plugin import IdfPytestEmbedded
|
|||||||
from idf_pytest.utils import format_case_id
|
from idf_pytest.utils import format_case_id
|
||||||
from pytest_embedded.plugin import multi_dut_argument
|
from pytest_embedded.plugin import multi_dut_argument
|
||||||
from pytest_embedded.plugin import multi_dut_fixture
|
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
|
||||||
|
|
||||||
@ -148,6 +155,113 @@ class BuildReportDownloader(AppDownloader):
|
|||||||
super().download_app(app_build_path, artifact_type)
|
super().download_app(app_build_path, artifact_type)
|
||||||
|
|
||||||
|
|
||||||
|
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) -> t.Optional['OpenOCD']:
|
||||||
|
return self.run()
|
||||||
|
|
||||||
|
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: IdfDut) -> OpenOCD:
|
||||||
|
if isinstance(dut, tuple):
|
||||||
|
raise ValueError('Multi-DUT support is not implemented yet')
|
||||||
|
return OpenOCD(dut)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def app_downloader(pipeline_id: t.Optional[str]) -> t.Optional[AppDownloader]:
|
def app_downloader(pipeline_id: t.Optional[str]) -> t.Optional[AppDownloader]:
|
||||||
if not pipeline_id:
|
if not pipeline_id:
|
||||||
|
@ -259,17 +259,32 @@ examples/system/sysview_tracing:
|
|||||||
disable:
|
disable:
|
||||||
- if: SOC_GPTIMER_SUPPORTED != 1
|
- if: SOC_GPTIMER_SUPPORTED != 1
|
||||||
disable_test:
|
disable_test:
|
||||||
- if: IDF_TARGET != "esp32"
|
- if: IDF_TARGET == "esp32p4"
|
||||||
temporary: true
|
temporary: true
|
||||||
reason: lack of runners
|
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:
|
examples/system/sysview_tracing_heap_log:
|
||||||
disable:
|
disable:
|
||||||
- if: SOC_GPTIMER_SUPPORTED != 1
|
- if: SOC_GPTIMER_SUPPORTED != 1
|
||||||
disable_test:
|
disable_test:
|
||||||
- if: IDF_TARGET != "esp32"
|
- if: IDF_TARGET == "esp32p4"
|
||||||
temporary: true
|
temporary: true
|
||||||
reason: lack of runners
|
reason: lack of runners
|
||||||
|
- if: IDF_TARGET == "esp32s3"
|
||||||
|
temporary: true
|
||||||
|
reason: unstable, known data corruption issue #TODO: OCD-992
|
||||||
|
- 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:
|
examples/system/task_watchdog:
|
||||||
disable:
|
disable:
|
||||||
|
@ -1,126 +1,19 @@
|
|||||||
# 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
|
||||||
from pytest_embedded_idf.utils import idf_parametrize
|
from pytest_embedded_idf.utils import idf_parametrize
|
||||||
|
|
||||||
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: '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.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
|
||||||
@ -151,17 +44,15 @@ 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.jtag
|
@pytest.mark.jtag
|
||||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||||
def test_examples_app_trace_basic(dut: IdfDut) -> None:
|
def test_examples_app_trace_basic(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
_test_examples_app_trace_basic(dut)
|
_test_examples_app_trace_basic(openocd, dut)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usb_serial_jtag
|
@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'], indirect=['target'])
|
||||||
def test_examples_app_trace_basic_usj(dut: IdfDut) -> None:
|
def test_examples_app_trace_basic_usj(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
_test_examples_app_trace_basic(dut)
|
_test_examples_app_trace_basic(openocd, dut)
|
||||||
|
@ -1,129 +1,23 @@
|
|||||||
# 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.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
|
||||||
from pytest_embedded_idf.utils import idf_parametrize
|
from pytest_embedded_idf.utils import idf_parametrize
|
||||||
|
|
||||||
MAX_RETRIES = 3
|
if typing.TYPE_CHECKING:
|
||||||
RETRY_DELAY = 1
|
from conftest import OpenOCD
|
||||||
TELNET_PORT = 4444
|
|
||||||
|
|
||||||
|
|
||||||
class OpenOCD:
|
def _test_gcov(openocd: '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}'],
|
||||||
@ -153,9 +47,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.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)
|
||||||
|
|
||||||
@ -177,17 +70,15 @@ 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
|
||||||
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||||
def test_gcov(dut: IdfDut) -> None:
|
def test_gcov(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
_test_gcov(dut)
|
_test_gcov(openocd, dut)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usb_serial_jtag
|
@pytest.mark.usb_serial_jtag
|
||||||
@idf_parametrize('target', ['esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'])
|
@idf_parametrize('target', ['esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'])
|
||||||
def test_gcov_usj(dut: IdfDut) -> None:
|
def test_gcov_usj(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
_test_gcov(dut)
|
_test_gcov(openocd, dut)
|
||||||
|
@ -134,11 +134,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[CONFIG_FREERTOS_NUMBER_OF_CORES];
|
static example_event_data_t event_data[CONFIG_FREERTOS_NUMBER_OF_CORES];
|
||||||
|
|
||||||
#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()) {
|
||||||
|
@ -1,48 +1,66 @@
|
|||||||
# 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 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
|
||||||
from pytest_embedded_idf.utils import idf_parametrize
|
from pytest_embedded_idf.utils import idf_parametrize
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from conftest import OpenOCD
|
||||||
|
|
||||||
|
|
||||||
|
def _test_examples_sysview_tracing(openocd: '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.jtag
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
'embedded_services',
|
|
||||||
[
|
|
||||||
'esp,idf,jtag',
|
|
||||||
],
|
|
||||||
indirect=True,
|
|
||||||
)
|
|
||||||
@idf_parametrize('target', ['esp32'], indirect=['target'])
|
|
||||||
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.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.jtag
|
||||||
|
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||||
|
def test_examples_sysview_tracing(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
|
_test_examples_sysview_tracing(openocd, dut)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usb_serial_jtag
|
||||||
|
@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'])
|
||||||
|
def test_examples_sysview_tracing_usj(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
|
_test_examples_sysview_tracing(openocd, dut)
|
||||||
|
@ -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
|
||||||
|
@ -73,6 +73,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
|
||||||
|
@ -1,48 +1,47 @@
|
|||||||
# 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 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
|
||||||
from pytest_embedded_idf.utils import idf_parametrize
|
from pytest_embedded_idf.utils import idf_parametrize
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from conftest import OpenOCD
|
||||||
|
|
||||||
@pytest.mark.jtag
|
|
||||||
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
|
def _test_examples_sysview_tracing_heap_log(openocd: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||||
@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:
|
|
||||||
# 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.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:
|
||||||
|
# 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
|
||||||
@ -50,9 +49,23 @@ 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
|
'Thread 2 "main" hit Temporary breakpoint 1, heap_trace_start (mode_param', timeout=10
|
||||||
) # should be (mode_param=HEAP_TRACE_ALL) # TODO GCC-329
|
) # 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
|
||||||
|
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||||
|
def test_examples_sysview_tracing_heap_log(openocd: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||||
|
_test_examples_sysview_tracing_heap_log(openocd, idf_path, dut)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
|
||||||
|
@pytest.mark.usb_serial_jtag
|
||||||
|
@idf_parametrize('target', ['esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'])
|
||||||
|
def test_examples_sysview_tracing_heap_log_usj(openocd: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
|
||||||
|
_test_examples_sysview_tracing_heap_log(openocd, idf_path, dut)
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"esp32c5": [
|
"esp32c5": [
|
||||||
|
@ -47,9 +47,12 @@ 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: 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:
|
tools/test_apps/system/gdb_loadable_elf:
|
||||||
disable_test:
|
disable_test:
|
||||||
|
@ -1,72 +1,19 @@
|
|||||||
# 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 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
|
||||||
from pytest_embedded_idf.utils import idf_parametrize
|
from pytest_embedded_idf.utils import idf_parametrize
|
||||||
|
|
||||||
MAX_RETRIES = 3
|
if typing.TYPE_CHECKING:
|
||||||
RETRY_DELAY = 3 # seconds
|
from conftest import OpenOCD
|
||||||
|
|
||||||
|
|
||||||
def run_openocd(dut: IdfDut) -> Optional[Popen]:
|
def _test_idf_gdb(openocd: '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!')
|
||||||
@ -74,36 +21,27 @@ 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.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, pexpect.spawn(
|
encoding='utf-8',
|
||||||
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',
|
re.compile(r'add symbol table from file.*rom.elf')
|
||||||
) as p:
|
) # if fail here: add target support here https://github.com/espressif/esp-rom-elfs
|
||||||
p.expect(re.compile(r'add symbol table from file.*bootloader.elf'))
|
p.expect_exact('hit Temporary breakpoint 1, app_main ()')
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.jtag
|
@pytest.mark.jtag
|
||||||
@idf_parametrize('target', ['esp32', 'esp32s2'], indirect=['target'])
|
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
|
||||||
def test_idf_gdb(dut: IdfDut) -> None:
|
def test_idf_gdb(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
_test_idf_gdb(dut)
|
_test_idf_gdb(openocd, dut)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usb_serial_jtag
|
@pytest.mark.usb_serial_jtag
|
||||||
@idf_parametrize('target', ['esp32c6', 'esp32h2'], indirect=['target'])
|
@idf_parametrize('target', ['esp32s3', 'esp32c3', 'esp32c6', 'esp32h2'], indirect=['target'])
|
||||||
def test_idf_gdb_usj(dut: IdfDut) -> None:
|
def test_idf_gdb_usj(openocd: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
_test_idf_gdb(dut)
|
_test_idf_gdb(openocd, dut)
|
||||||
|
Reference in New Issue
Block a user