mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-30 18:57:19 +02:00
ci: enable gcov example for all chips
This commit is contained in:
@ -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:
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -1 +0,0 @@
|
|||||||
CONFIG_FREERTOS_UNICORE=y
|
|
||||||
|
Reference in New Issue
Block a user