ci: OpenOCD class as fixture

This commit is contained in:
Samuel Obuch
2025-05-20 10:44:33 +02:00
parent 8cee39c53c
commit c2dd5c35e0
5 changed files with 169 additions and 486 deletions

View File

@@ -13,14 +13,19 @@
import logging import logging
import os import os
import re import re
import signal
import sys import sys
import time
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from fnmatch import fnmatch from fnmatch import fnmatch
from telnetlib import Telnet
from typing import Any
from typing import Callable from typing import Callable
from typing import List from typing import List
from typing import Optional from typing import Optional
from typing import Tuple from typing import Tuple
import pexpect
import pytest import pytest
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import ExitCode from _pytest.config import ExitCode
@@ -34,6 +39,8 @@ from _pytest.terminal import TerminalReporter
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 find_by_suffix from pytest_embedded.utils import find_by_suffix
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
@@ -49,6 +56,12 @@ except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages')) sys.path.append(os.path.join(os.path.dirname(__file__), 'tools', 'ci', 'python_packages'))
import common_test_methods # noqa: F401 import common_test_methods # noqa: F401
try:
from idf_py_actions.debug_ext import get_openocd_arguments
except ModuleNotFoundError:
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from idf_py_actions.debug_ext import get_openocd_arguments
SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2'] SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2']
PREVIEW_TARGETS: List[str] = [] # this PREVIEW_TARGETS excludes 'linux' target PREVIEW_TARGETS: List[str] = [] # this PREVIEW_TARGETS excludes 'linux' target
DEFAULT_SDKCONFIG = 'default' DEFAULT_SDKCONFIG = 'default'
@@ -256,6 +269,103 @@ def test_case_name(request: FixtureRequest, target: str, config: str) -> str:
return format_case_id(target, config, request.node.originalname) return format_case_id(target, config, request.node.originalname)
class OpenOCD:
def __init__(self, dut: 'IdfDut'):
self.MAX_RETRIES = 3
self.RETRY_DELAY = 1
self.TELNET_PORT = 4444
self.dut = dut
self.telnet: Optional[Telnet] = None
self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
self.proc: Optional[pexpect.spawn] = None
def __enter__(self) -> 'OpenOCD':
return self
def __exit__(self, exception_type: Any, exception_value: Any, exception_traceback: Any) -> None:
self.kill()
def run(self) -> Optional['OpenOCD']:
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
raise RuntimeError('OPENOCD_SCRIPTS environment variable is not set.')
debug_args = get_openocd_arguments(self.dut.target)
assert debug_args
# For debug purposes, make the value '4'
ocd_env = os.environ.copy()
ocd_env['LIBUSB_DEBUG'] = '1'
for _ in range(1, self.MAX_RETRIES + 1):
try:
self.proc = pexpect.spawn(
command='openocd',
args=['-s', openocd_scripts] + debug_args.split(),
timeout=5,
encoding='utf-8',
codec_errors='ignore',
env=ocd_env,
)
if self.proc and self.proc.isalive():
self.proc.expect_exact('Info : Listening on port 3333 for gdb connections', timeout=5)
self.connect_telnet()
self.write('log_output {}'.format(self.log_file))
return self
except (pexpect.exceptions.EOF, pexpect.exceptions.TIMEOUT, ConnectionRefusedError) as e:
logging.error('Error running OpenOCD: %s', str(e))
self.kill()
time.sleep(self.RETRY_DELAY)
raise RuntimeError('Failed to run OpenOCD after %d attempts.', self.MAX_RETRIES)
def connect_telnet(self) -> None:
for attempt in range(1, self.MAX_RETRIES + 1):
try:
self.telnet = Telnet('127.0.0.1', self.TELNET_PORT, 5)
break
except ConnectionRefusedError as e:
logging.error('Error telnet connection: %s in attempt:%d', e, attempt)
time.sleep(1)
else:
raise ConnectionRefusedError
def write(self, s: str) -> Any:
if self.telnet is None:
logging.error('Telnet connection is not established.')
return ''
resp = self.telnet.read_very_eager()
self.telnet.write(to_bytes(s, '\n'))
resp += self.telnet.read_until(b'>')
return to_str(resp)
def apptrace_wait_stop(self, timeout: int = 30) -> None:
stopped = False
end_before = time.time() + timeout
while not stopped:
cmd_out = self.write('esp apptrace status')
for line in cmd_out.splitlines():
if line.startswith('Tracing is STOPPED.'):
stopped = True
break
if not stopped and time.time() > end_before:
raise pexpect.TIMEOUT('Failed to wait for apptrace stop!')
time.sleep(1)
def kill(self) -> None:
# Check if the process is still running
if self.proc and self.proc.isalive():
self.proc.terminate()
self.proc.kill(signal.SIGKILL)
@pytest.fixture
def openocd_dut(dut: IdfDut) -> OpenOCD:
if isinstance(dut, tuple):
raise ValueError('Multi-DUT support is not implemented yet')
return OpenOCD(dut)
@pytest.fixture @pytest.fixture
@multi_dut_fixture @multi_dut_fixture
def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str: def build_dir(app_path: str, target: Optional[str], config: Optional[str]) -> str:

View File

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

View File

@@ -1,123 +1,22 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging import os.path
import os
import signal
import sys
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
try: if typing.TYPE_CHECKING:
from idf_py_actions.debug_ext import get_openocd_arguments from conftest import OpenOCD
except ModuleNotFoundError:
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
from idf_py_actions.debug_ext import get_openocd_arguments
MAX_RETRIES = 3
RETRY_DELAY = 1
TELNET_PORT = 4444
class OpenOCD: def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
def __init__(self, dut: 'IdfDut'):
self.dut = dut
self.telnet: Optional[Telnet] = None
self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
self.proc: Optional[pexpect.spawn] = None
def run(self) -> Optional['OpenOCD']:
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
logging.error('OPENOCD_SCRIPTS environment variable is not set.')
return None
debug_args = get_openocd_arguments(self.dut.target)
assert debug_args
# 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}'],
@@ -147,9 +46,8 @@ def _test_gcov(dut: IdfDut) -> None:
assert len(expect_lines) == 0 assert len(expect_lines) == 0
try: dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
openocd.connect_telnet() with openocd_dut.run() as openocd:
openocd.write('log_output {}'.format(openocd.log_file))
openocd.write('reset run') openocd.write('reset run')
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
@@ -171,16 +69,14 @@ def _test_gcov(dut: IdfDut) -> None:
time.sleep(1) time.sleep(1)
# Test instant run-time dump # Test instant run-time dump
dump_coverage('esp gcov') dump_coverage('esp gcov')
finally:
openocd.kill()
@pytest.mark.jtag @pytest.mark.jtag
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.esp32c2 @pytest.mark.esp32c2
@pytest.mark.esp32s2 @pytest.mark.esp32s2
def test_gcov(dut: IdfDut) -> None: def test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_gcov(dut) _test_gcov(openocd_dut, dut)
@pytest.mark.esp32s3 @pytest.mark.esp32s3
@@ -188,5 +84,5 @@ def test_gcov(dut: IdfDut) -> None:
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.esp32h2 @pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag @pytest.mark.usb_serial_jtag
def test_gcov_usj(dut: IdfDut) -> None: def test_gcov_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_gcov(dut) _test_gcov(openocd_dut, dut)

View File

@@ -1,120 +1,19 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os.path import os.path
import re import re
import signal
import time import time
from telnetlib import Telnet import typing
from typing import Any
from typing import Optional
import pexpect import pexpect
import pytest import pytest
from pytest_embedded.utils import to_bytes
from pytest_embedded.utils import to_str
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
MAX_RETRIES = 3 if typing.TYPE_CHECKING:
RETRY_DELAY = 1 from conftest import OpenOCD
TELNET_PORT = 4444
class OpenOCD: def _test_examples_sysview_tracing(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
def __init__(self, dut: 'IdfDut'):
self.dut = dut
self.telnet: Optional[Telnet] = None
self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
self.proc: Optional[pexpect.spawn] = None
def run(self) -> Optional['OpenOCD']:
desc_path = os.path.join(self.dut.app.binary_path, 'project_description.json')
try:
with open(desc_path, 'r') as f:
project_desc = json.load(f)
except FileNotFoundError:
logging.error('Project description file not found at %s', desc_path)
return None
openocd_scripts = os.getenv('OPENOCD_SCRIPTS')
if not openocd_scripts:
logging.error('OPENOCD_SCRIPTS environment variable is not set.')
return None
debug_args = project_desc.get('debug_arguments_openocd')
if not debug_args:
logging.error("'debug_arguments_openocd' key is missing in project_description.json")
return None
# For debug purposes, make the value '4'
ocd_env = os.environ.copy()
ocd_env['LIBUSB_DEBUG'] = '1'
for _ in range(1, MAX_RETRIES + 1):
try:
self.proc = pexpect.spawn(
command='openocd',
args=['-s', openocd_scripts] + debug_args.split(),
timeout=5,
encoding='utf-8',
codec_errors='ignore',
env=ocd_env,
)
if self.proc and self.proc.isalive():
self.proc.expect_exact('Info : Listening on port 3333 for gdb connections', timeout=5)
return self
except (pexpect.exceptions.EOF, pexpect.exceptions.TIMEOUT) as e:
logging.error('Error running OpenOCD: %s', str(e))
if self.proc and self.proc.isalive():
self.proc.terminate()
time.sleep(RETRY_DELAY)
logging.error('Failed to run OpenOCD after %d attempts.', MAX_RETRIES)
return None
def connect_telnet(self) -> None:
for attempt in range(1, MAX_RETRIES + 1):
try:
self.telnet = Telnet('127.0.0.1', TELNET_PORT, 5)
break
except ConnectionRefusedError as e:
logging.error('Error telnet connection: %s in attempt:%d', e, attempt)
time.sleep(1)
else:
raise ConnectionRefusedError
def write(self, s: str) -> Any:
if self.telnet is None:
logging.error('Telnet connection is not established.')
return ''
resp = self.telnet.read_very_eager()
self.telnet.write(to_bytes(s, '\n'))
resp += self.telnet.read_until(b'>')
return to_str(resp)
def apptrace_wait_stop(self, timeout: int = 30) -> None:
stopped = False
end_before = time.time() + timeout
while not stopped:
cmd_out = self.write('esp apptrace status')
for line in cmd_out.splitlines():
if line.startswith('Tracing is STOPPED.'):
stopped = True
break
if not stopped and time.time() > end_before:
raise pexpect.TIMEOUT('Failed to wait for apptrace stop!')
time.sleep(1)
def kill(self) -> None:
# Check if the process is still running
if self.proc and self.proc.isalive():
self.proc.terminate()
self.proc.kill(signal.SIGKILL)
def _test_examples_sysview_tracing(dut: IdfDut) -> None:
# Construct trace log paths # Construct trace log paths
trace_log = [ trace_log = [
os.path.join(dut.logdir, 'sys_log0.svdat') # pylint: disable=protected-access os.path.join(dut.logdir, 'sys_log0.svdat') # pylint: disable=protected-access
@@ -138,13 +37,7 @@ 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(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) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
openocd = OpenOCD(dut).run() with openocd_dut.run() as openocd, open(gdb_logfile, 'w') as gdb_log, pexpect.spawn(
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 --gdbinit {gdbinit}', f'idf.py -B {dut.app.binary_path} gdb --batch --gdbinit {gdbinit}',
timeout=60, timeout=60,
logfile=gdb_log, logfile=gdb_log,
@@ -158,16 +51,14 @@ def _test_examples_sysview_tracing(dut: IdfDut) -> None:
# Do a sleep while sysview samples are captured. # Do a sleep while sysview samples are captured.
time.sleep(3) time.sleep(3)
openocd.write('esp sysview stop') openocd.write('esp sysview stop')
finally:
openocd.kill()
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.esp32s2 @pytest.mark.esp32s2
@pytest.mark.esp32c2 @pytest.mark.esp32c2
@pytest.mark.jtag @pytest.mark.jtag
def test_examples_sysview_tracing(dut: IdfDut) -> None: def test_examples_sysview_tracing(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_examples_sysview_tracing(dut) _test_examples_sysview_tracing(openocd_dut, dut)
@pytest.mark.esp32s3 @pytest.mark.esp32s3
@@ -175,5 +66,5 @@ def test_examples_sysview_tracing(dut: IdfDut) -> None:
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.esp32h2 @pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag @pytest.mark.usb_serial_jtag
def test_examples_sysview_tracing_usj(openocd: 'OpenOCD', dut: IdfDut) -> None: def test_examples_sysview_tracing_usj(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
_test_examples_sysview_tracing(openocd, dut) _test_examples_sysview_tracing(openocd_dut, dut)

View File

@@ -1,119 +1,17 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os.path import os.path
import signal import typing
import time
from telnetlib import Telnet
from typing import Any
from typing import Optional
import pexpect.fdpexpect import pexpect.fdpexpect
import pytest import pytest
from pytest_embedded.utils import to_bytes
from pytest_embedded.utils import to_str
from pytest_embedded_idf import IdfDut from pytest_embedded_idf import IdfDut
MAX_RETRIES = 3 if typing.TYPE_CHECKING:
RETRY_DELAY = 1 from conftest import OpenOCD
TELNET_PORT = 4444
class OpenOCD: def _test_examples_sysview_tracing_heap_log(openocd_dut: 'OpenOCD', idf_path: str, 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_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(dut.logdir, 'heap_log0.svdat') # pylint: disable=protected-access os.path.join(dut.logdir, 'heap_log0.svdat') # pylint: disable=protected-access
@@ -134,13 +32,7 @@ def _test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
f_w.write(line) f_w.write(line)
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
openocd = OpenOCD(dut).run() with openocd_dut.run(), open(gdb_logfile, 'w') as gdb_log, pexpect.spawn(
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 --gdbinit {gdbinit}', f'idf.py -B {dut.app.binary_path} gdb --batch --gdbinit {gdbinit}',
timeout=60, timeout=60,
logfile=gdb_log, logfile=gdb_log,
@@ -149,8 +41,6 @@ def _test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
) as p: ) as p:
# Wait for sysview files to be generated # Wait for sysview files to be generated
p.expect_exact('Tracing is STOPPED') p.expect_exact('Tracing is STOPPED')
finally:
openocd.kill()
# 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
@@ -170,8 +60,8 @@ def _test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.esp32s2 @pytest.mark.esp32s2
@pytest.mark.esp32c2 @pytest.mark.esp32c2
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:
_test_examples_sysview_tracing_heap_log(idf_path, dut) _test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut)
@pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True) @pytest.mark.parametrize('config', ['app_trace_jtag'], indirect=True)
@@ -180,5 +70,5 @@ def test_examples_sysview_tracing_heap_log(idf_path: str, dut: IdfDut) -> None:
@pytest.mark.esp32c3 @pytest.mark.esp32c3
@pytest.mark.esp32c6 @pytest.mark.esp32c6
@pytest.mark.esp32h2 @pytest.mark.esp32h2
def test_examples_sysview_tracing_heap_log_usj(openocd: 'OpenOCD', idf_path: str, dut: IdfDut) -> None: def test_examples_sysview_tracing_heap_log_usj(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfDut) -> None:
_test_examples_sysview_tracing_heap_log(openocd, idf_path, dut) _test_examples_sysview_tracing_heap_log(openocd_dut, idf_path, dut)