Merge branch 'ci/enable_gcov_test_v5.5' into 'release/v5.5'

ci: enable gcov example for all chips (v5.5)

See merge request espressif/esp-idf!39162
This commit is contained in:
Alexey Gerenkov
2025-05-20 01:58:20 +08:00
8 changed files with 179 additions and 35 deletions

View File

@ -1,8 +1,15 @@
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
examples/system/app_trace_basic: examples/system/app_trace_basic:
disable:
- if: IDF_TARGET == "esp32h21"
temporary: true
reason: not supported yet #TODO: OCD-1081
- if: IDF_TARGET == "esp32h4"
temporary: true
reason: not supported yet #TODO: OCD-1137
disable_test: disable_test:
- if: IDF_TARGET in ["esp32p4", "esp32h21"] - if: IDF_TARGET == "esp32p4"
temporary: true temporary: true
reason: lack of runners. reason: lack of runners.
@ -85,10 +92,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

@ -1,5 +1,5 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | 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 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | | ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- | -------- |
# Application Level Tracing Example (Basic) # Application Level Tracing Example (Basic)

View File

@ -131,8 +131,8 @@ def _test_examples_app_trace_basic(dut: IdfDut) -> None:
with open(openocd.log_file, encoding='utf-8') as oocd_log: # pylint: disable=protected-access with open(openocd.log_file, encoding='utf-8') as oocd_log: # pylint: disable=protected-access
cores = 1 if dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') is True else 2 cores = 1 if dut.app.sdkconfig.get('ESP_SYSTEM_SINGLE_CORE_MODE') is True else 2
search_strings.append('App trace params: from {} cores,'.format(cores)) search_strings.append('App trace params: from {} cores,'.format(cores))
found = False
for search_str in search_strings: for search_str in search_strings:
found = False
oocd_log.seek(0) oocd_log.seek(0)
for line in oocd_log: for line in oocd_log:
if search_str in line: if search_str in line:

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,18 +153,41 @@ 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
@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