ci: enable gcov example for all chips

This commit is contained in:
Samuel Obuch
2025-04-28 15:58:35 +02:00
parent f0b6cc433c
commit 9e03a7953a
4 changed files with 160 additions and 29 deletions

View File

@ -98,9 +98,12 @@ examples/system/freertos:
examples/system/gcov: examples/system/gcov:
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-1048
examples/system/gdbstub: examples/system/gdbstub:
disable: disable:

View File

@ -12,17 +12,22 @@
#include "driver/gpio.h" #include "driver/gpio.h"
#include "esp_app_trace.h" #include "esp_app_trace.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include "esp_log.h"
/* Can use project configuration menu (idf.py menuconfig) to choose the GPIO /* Can use project configuration menu (idf.py menuconfig) to choose the GPIO
to blink, or you can edit the following line and set a number here. to blink, or you can edit the following line and set a number here.
*/ */
#define BLINK_GPIO CONFIG_BLINK_GPIO #define BLINK_GPIO CONFIG_BLINK_GPIO
static const char *TAG = "example";
void blink_dummy_func(void); void blink_dummy_func(void);
void some_dummy_func(void); void some_dummy_func(void);
static void blink_task(void *pvParameter) static void blink_task(void *pvParameter)
{ {
ESP_LOGI(TAG, "Ready for OpenOCD connection");
// The first two iterations GCOV data are dumped using call to esp_gcov_dump() and OOCD's "esp32 gcov dump" command. // The first two iterations GCOV data are dumped using call to esp_gcov_dump() and OOCD's "esp32 gcov dump" command.
// After that they can be dumped using OOCD's "esp32 gcov" command only. // After that they can be dumped using OOCD's "esp32 gcov" command only.
int dump_gcov_after = -2; int dump_gcov_after = -2;

View File

@ -1,28 +1,128 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0 # SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import logging
import os.path import os.path
import signal
import time import time
from telnetlib import Telnet
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_jtag import OpenOcd
MAX_RETRIES = 3
RETRY_DELAY = 1
TELNET_PORT = 4444
@pytest.mark.esp32 class OpenOCD:
@pytest.mark.jtag def __init__(self, dut: 'IdfDut'):
@pytest.mark.parametrize( self.dut = dut
'embedded_services, no_gdb', self.telnet: Optional[Telnet] = None
[ self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
('esp,idf,jtag', 'y'), self.proc: Optional[pexpect.spawn] = None
],
indirect=True, def run(self) -> Optional['OpenOCD']:
) desc_path = os.path.join(self.dut.app.binary_path, 'project_description.json')
def test_gcov(dut: IdfDut, openocd: OpenOcd) -> None:
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}'],
@ -30,9 +130,6 @@ def test_gcov(dut: IdfDut, openocd: OpenOcd) -> None:
timeout=timeout, timeout=timeout,
) )
expect_counter_output(0)
dut.expect('Ready to dump GCOV data...', timeout=5)
def dump_coverage(cmd: str) -> None: def dump_coverage(cmd: str) -> None:
response = openocd.write(cmd) response = openocd.write(cmd)
@ -55,18 +152,45 @@ def test_gcov(dut: IdfDut, openocd: OpenOcd) -> None:
assert len(expect_lines) == 0 assert len(expect_lines) == 0
# Test two hard-coded dumps try:
dump_coverage('esp gcov dump') openocd.connect_telnet()
dut.expect('GCOV data have been dumped.', timeout=5) openocd.write('log_output {}'.format(openocd.log_file))
expect_counter_output(1) openocd.write('reset run')
dut.expect('Ready to dump GCOV data...', timeout=5) dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5)
for i in range(2, 6): expect_counter_output(0)
expect_counter_output(i) dut.expect('Ready to dump GCOV data...', timeout=5)
for _ in range(3): # Test two hard-coded dumps
time.sleep(1) dump_coverage('esp gcov dump')
# Test instant run-time dump dut.expect('GCOV data have been dumped.', timeout=5)
dump_coverage('esp gcov') expect_counter_output(1)
dut.expect('Ready to dump GCOV data...', timeout=5)
dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5)
for i in range(2, 6):
expect_counter_output(i)
for _ in range(3):
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)
@pytest.mark.esp32c3
@pytest.mark.esp32c6
@pytest.mark.esp32h2
@pytest.mark.usb_serial_jtag
def test_gcov_usj(dut: IdfDut) -> None:
_test_gcov(dut)

View File

@ -1 +0,0 @@
CONFIG_FREERTOS_UNICORE=y