ci: enable gcov example for all chips

This commit is contained in:
Samuel Obuch
2025-04-28 15:58:35 +02:00
parent b9e03c3cf4
commit dd1a331f4f
6 changed files with 168 additions and 31 deletions

View File

@@ -85,10 +85,20 @@ examples/system/freertos/real_time_stats:
- freertos - freertos
examples/system/gcov: examples/system/gcov:
disable:
- if: IDF_TARGET == "esp32h21"
temporary: true
reason: not supported yet #TODO: OCD-1079
- if: IDF_TARGET == "esp32h4"
temporary: true
reason: not supported yet #TODO: OCD-1138
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

@@ -2,6 +2,9 @@
# in this exact order for cmake to work correctly # in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# keep this string to detect as project file in CI:
#include($ENV{IDF_PATH}/tools/cmake/project.cmake)
file(TO_NATIVE_PATH "$ENV{IDF_PATH}/tools/cmake/project.cmake" _project_path) file(TO_NATIVE_PATH "$ENV{IDF_PATH}/tools/cmake/project.cmake" _project_path)
include(${_project_path}) include(${_project_path})

View File

@@ -1,5 +1,5 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | | Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
# Blink Example With Coverage Info (Gcov) # Blink Example With Coverage Info (Gcov)

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,29 +1,129 @@
# 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
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
from pytest_embedded_jtag import OpenOcd
MAX_RETRIES = 3
RETRY_DELAY = 1
TELNET_PORT = 4444
@pytest.mark.jtag class OpenOCD:
@pytest.mark.parametrize( def __init__(self, dut: 'IdfDut'):
'embedded_services, no_gdb', self.dut = dut
[ self.telnet: Optional[Telnet] = None
('esp,idf,jtag', 'y'), self.log_file = os.path.join(self.dut.logdir, 'ocd.txt')
], self.proc: Optional[pexpect.spawn] = None
indirect=True,
) def run(self) -> Optional['OpenOCD']:
@idf_parametrize('target', ['esp32'], indirect=['target']) 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}'],
@@ -31,9 +131,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)
@@ -56,6 +153,15 @@ def test_gcov(dut: IdfDut, openocd: OpenOcd) -> None:
assert len(expect_lines) == 0 assert len(expect_lines) == 0
try:
openocd.connect_telnet()
openocd.write('log_output {}'.format(openocd.log_file))
openocd.write('reset run')
dut.expect_exact('example: Ready for OpenOCD connection', timeout=5)
expect_counter_output(0)
dut.expect('Ready to dump GCOV data...', timeout=5)
# Test two hard-coded dumps # Test two hard-coded dumps
dump_coverage('esp gcov dump') dump_coverage('esp gcov dump')
dut.expect('GCOV data have been dumped.', timeout=5) dut.expect('GCOV data have been dumped.', timeout=5)
@@ -71,3 +177,17 @@ def test_gcov(dut: IdfDut, openocd: OpenOcd) -> 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
@idf_parametrize('target', ['esp32', 'esp32c2', 'esp32s2'], indirect=['target'])
def test_gcov(dut: IdfDut) -> None:
_test_gcov(dut)
@pytest.mark.usb_serial_jtag
@idf_parametrize('target', ['esp32c3', 'esp32c5', 'esp32c6', 'esp32c61', 'esp32h2'], indirect=['target'])
def test_gcov_usj(dut: IdfDut) -> None:
_test_gcov(dut)

View File

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