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

@@ -1,123 +1,22 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import os
import signal
import sys
import os.path
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
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
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']:
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:
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}'],
@@ -147,9 +46,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)
@@ -171,16 +69,14 @@ def _test_gcov(dut: IdfDut) -> None:
time.sleep(1)
# Test instant run-time dump
dump_coverage('esp gcov')
finally:
openocd.kill()
@pytest.mark.jtag
@pytest.mark.esp32
@pytest.mark.esp32c2
@pytest.mark.esp32s2
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.esp32s3
@@ -188,5 +84,5 @@ def test_gcov(dut: IdfDut) -> None:
@pytest.mark.esp32c6
@pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag
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)