mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
test(gcov): resolve component CMakeFiles dir dynamically
pytest_gcov hardcoded `__idf_<component>.dir/` as the per-component CMake intermediate directory where each .gcda is expected to land. The directory name is build-system specific (Build system v1 uses `__idf_<component>.dir`, Build system v2 uses `_idf_<component>.dir`). Asserting on a single literal made every test_gcov_uart run fail under Build system v2 even though the chip was streaming valid coverage data and the host-side UartGcovCapture was writing the files to the build's actual layout. Probe `CMakeFiles/*_<component>.dir/` and prefer the candidate whose .gcno files were emitted by the active build, so the assertion follows the build output rather than dictating it.
This commit is contained in:
@@ -25,44 +25,68 @@ except ImportError:
|
|||||||
from gcov_capture import get_coverage_data
|
from gcov_capture import get_coverage_data
|
||||||
|
|
||||||
|
|
||||||
|
def _candidate_target_dirs(binary_path: str, component: str) -> list:
|
||||||
|
"""Per-component CMakeFiles/<target>.dir/ directories used by either build system.
|
||||||
|
|
||||||
|
Build system v1 emits ``__idf_<component>.dir`` (double underscore); Build system
|
||||||
|
v2 emits ``_idf_<component>.dir`` (single underscore). Both candidates are returned
|
||||||
|
so the caller can probe either layout without knowing which build system produced
|
||||||
|
the binary.
|
||||||
|
"""
|
||||||
|
parent = os.path.join(binary_path, 'esp-idf', component, 'CMakeFiles')
|
||||||
|
return [
|
||||||
|
os.path.join(parent, f'__idf_{component}.dir'),
|
||||||
|
os.path.join(parent, f'_idf_{component}.dir'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_gcda_path(binary_path: str, component: str, basename: str) -> str:
|
||||||
|
"""Return the .gcda path, preferring whichever build-system layout already has the file.
|
||||||
|
|
||||||
|
Before the chip dumps, neither layout has a .gcda yet, so the chip-side FOPEN will
|
||||||
|
create the directory of whichever path the binary embedded. Call this again after
|
||||||
|
the dump to get the materialized path.
|
||||||
|
"""
|
||||||
|
for d in _candidate_target_dirs(binary_path, component):
|
||||||
|
candidate = os.path.join(d, basename)
|
||||||
|
if os.path.isfile(candidate):
|
||||||
|
return candidate
|
||||||
|
# Default to v2 layout; the post-dump call will return the actual path.
|
||||||
|
return os.path.join(_candidate_target_dirs(binary_path, component)[1], basename)
|
||||||
|
|
||||||
|
|
||||||
def get_expected_gcda_paths(dut: IdfDut) -> list:
|
def get_expected_gcda_paths(dut: IdfDut) -> list:
|
||||||
"""Get list of expected .gcda file paths for this example."""
|
"""Get list of expected .gcda file paths for this example."""
|
||||||
return [
|
return [
|
||||||
os.path.join(
|
_resolve_gcda_path(dut.app.binary_path, 'main', 'gcov_example_main.c.gcda'),
|
||||||
dut.app.binary_path, 'esp-idf', 'main', 'CMakeFiles', '__idf_main.dir', 'gcov_example_main.c.gcda'
|
_resolve_gcda_path(dut.app.binary_path, 'main', 'gcov_example_func.c.gcda'),
|
||||||
),
|
_resolve_gcda_path(dut.app.binary_path, 'sample', 'some_funcs.c.gcda'),
|
||||||
os.path.join(
|
|
||||||
dut.app.binary_path, 'esp-idf', 'main', 'CMakeFiles', '__idf_main.dir', 'gcov_example_func.c.gcda'
|
|
||||||
),
|
|
||||||
os.path.join(dut.app.binary_path, 'esp-idf', 'sample', 'CMakeFiles', '__idf_sample.dir', 'some_funcs.c.gcda'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def prepare_test(dut: IdfDut) -> list:
|
def prepare_test(dut: IdfDut) -> list:
|
||||||
"""Prepare test environment: create directories and clean up old .gcda files.
|
"""Prepare test environment: create both candidate directories and clean stale .gcda."""
|
||||||
|
# Pre-create both build-system layouts so the chip-side FOPEN can write to whichever
|
||||||
Returns list of expected .gcda file paths.
|
# the binary embedded, and clean stale .gcda from either layout.
|
||||||
"""
|
for component in ('main', 'sample'):
|
||||||
# Create the generated .gcda folders
|
for d in _candidate_target_dirs(dut.app.binary_path, component):
|
||||||
# Normally created via `idf.py build`, but in CI non-related files aren't preserved
|
os.makedirs(d, 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)
|
|
||||||
|
|
||||||
# Get expected paths and clean up old files
|
|
||||||
expected_gcda_paths = get_expected_gcda_paths(dut)
|
|
||||||
for gcda_path in expected_gcda_paths:
|
|
||||||
if os.path.isfile(gcda_path):
|
|
||||||
try:
|
try:
|
||||||
os.remove(gcda_path)
|
for entry in os.listdir(d):
|
||||||
print(f'Removed old .gcda file: {os.path.basename(gcda_path)}')
|
if entry.endswith('.gcda'):
|
||||||
except OSError as e:
|
try:
|
||||||
print(f'Warning: Could not remove {gcda_path}: {e}')
|
os.remove(os.path.join(d, entry))
|
||||||
|
print(f'Removed old .gcda file: {entry}')
|
||||||
|
except OSError as e:
|
||||||
|
print(f'Warning: Could not remove {os.path.join(d, entry)}: {e}')
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
return expected_gcda_paths
|
return get_expected_gcda_paths(dut)
|
||||||
|
|
||||||
|
|
||||||
def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||||
expected_gcda_paths = prepare_test(dut)
|
prepare_test(dut)
|
||||||
|
|
||||||
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(
|
||||||
@@ -95,6 +119,8 @@ def _test_gcov(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
|||||||
|
|
||||||
# Verify execution counts if expected and coverage data is available
|
# Verify execution counts if expected and coverage data is available
|
||||||
if expected_counts:
|
if expected_counts:
|
||||||
|
# Re-resolve gcda paths after the dump so we observe the layout the chip actually used.
|
||||||
|
expected_gcda_paths = get_expected_gcda_paths(dut)
|
||||||
coverage = get_coverage_data(dut.app.binary_path, expected_gcda_paths)
|
coverage = get_coverage_data(dut.app.binary_path, expected_gcda_paths)
|
||||||
if coverage: # Only verify if detailed data is available
|
if coverage: # Only verify if detailed data is available
|
||||||
print(f'Coverage data: {coverage}')
|
print(f'Coverage data: {coverage}')
|
||||||
@@ -161,7 +187,7 @@ def _test_gcov_uart(dut: IdfDut) -> None:
|
|||||||
# Close console port to free up the port for the gcov capture
|
# Close console port to free up the port for the gcov capture
|
||||||
dut.serial.close()
|
dut.serial.close()
|
||||||
|
|
||||||
expected_gcda_paths = prepare_test(dut)
|
prepare_test(dut)
|
||||||
log_file = os.path.join(dut.logdir, 'gcov_uart.log')
|
log_file = os.path.join(dut.logdir, 'gcov_uart.log')
|
||||||
uart_port = dut.serial.port
|
uart_port = dut.serial.port
|
||||||
baud = dut.app.sdkconfig.get('APPTRACE_UART_BAUDRATE')
|
baud = dut.app.sdkconfig.get('APPTRACE_UART_BAUDRATE')
|
||||||
@@ -178,6 +204,8 @@ def _test_gcov_uart(dut: IdfDut) -> None:
|
|||||||
# Verify each dump
|
# Verify each dump
|
||||||
for dump_num, expected in enumerate(expected_counts, start=1):
|
for dump_num, expected in enumerate(expected_counts, start=1):
|
||||||
if uart_capture.wait_for_fstop(timeout=10.0):
|
if uart_capture.wait_for_fstop(timeout=10.0):
|
||||||
|
# Re-resolve gcda paths after the dump so we observe the layout the chip actually used.
|
||||||
|
expected_gcda_paths = get_expected_gcda_paths(dut)
|
||||||
coverage = get_coverage_data(dut.app.binary_path, expected_gcda_paths)
|
coverage = get_coverage_data(dut.app.binary_path, expected_gcda_paths)
|
||||||
|
|
||||||
if coverage: # Only verify details if coverage data is available
|
if coverage: # Only verify details if coverage data is available
|
||||||
|
|||||||
Reference in New Issue
Block a user