mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
fix(ble_log_console): exclude from idf-ci pytest collection to fix CI
idf-ci discovers pyproject.toml pytest config and runs a separate collection pass with --target all, which finds 0 target tests and raises RuntimeError. Add to .idf_ci.toml exclude_dirs instead of deleting tests. Restore all 227 unit tests with lint/mypy fixes.
This commit is contained in:
@@ -7,6 +7,7 @@ exclude_dirs = [
|
|||||||
'tools/test_bsasm',
|
'tools/test_bsasm',
|
||||||
'tools/ci/test_autocomplete',
|
'tools/ci/test_autocomplete',
|
||||||
'tools/test_build_system',
|
'tools/test_build_system',
|
||||||
|
'tools/bt/ble_log_console',
|
||||||
]
|
]
|
||||||
|
|
||||||
[local_runtime_envs]
|
[local_runtime_envs]
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ menu "BT Logs"
|
|||||||
bool "Enable Controller logs"
|
bool "Enable Controller logs"
|
||||||
default n
|
default n
|
||||||
|
|
||||||
choice
|
choice BT_LE_CONTROLLER_LOG_OUTPUT_MODE
|
||||||
depends on BT_LE_CONTROLLER_LOG_ENABLED
|
depends on BT_LE_CONTROLLER_LOG_ENABLED
|
||||||
prompt "Controller log output mode"
|
prompt "Controller log output mode"
|
||||||
default BT_LE_CONTROLLER_LOG_MODE_BLE_LOG_V2 if BLE_LOG_ENABLED
|
default BT_LE_CONTROLLER_LOG_MODE_BLE_LOG_V2 if BLE_LOG_ENABLED
|
||||||
|
|||||||
@@ -232,6 +232,24 @@ if BLE_LOG_ENABLED
|
|||||||
menu "Settings of BLE Log Compression"
|
menu "Settings of BLE Log Compression"
|
||||||
source "$IDF_PATH/components/bt/common/ble_log/extension/log_compression/Kconfig.in"
|
source "$IDF_PATH/components/bt/common/ble_log/extension/log_compression/Kconfig.in"
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
|
# Deprecated options -- retained for backward sdkconfig compatibility.
|
||||||
|
# These symbols have no prompt so they never appear in menuconfig.
|
||||||
|
config BLE_LOG_LBM_TRANS_SIZE
|
||||||
|
int
|
||||||
|
default 512
|
||||||
|
|
||||||
|
config BLE_LOG_ENH_STAT_ENABLED
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
|
||||||
|
config BLE_LOG_PAYLOAD_CHECKSUM_ENABLED
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
|
||||||
|
config BLE_LOG_LBM_LL_TRANS_SIZE
|
||||||
|
int
|
||||||
|
default 512
|
||||||
endif
|
endif
|
||||||
|
|
||||||
menu "Legacy SPI Log Output (Deprecated - use BT Log Async Output instead)"
|
menu "Legacy SPI Log Output (Deprecated - use BT Log Async Output instead)"
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class StatsAccumulator:
|
|||||||
|
|
||||||
self._enh_stat_prev[src_code] = (written_frames, lost_frames, written_bytes, lost_bytes)
|
self._enh_stat_prev[src_code] = (written_frames, lost_frames, written_bytes, lost_bytes)
|
||||||
self._fw_written.record(src_code, written_frames, written_bytes)
|
self._fw_written.record(src_code, written_frames, written_bytes)
|
||||||
return self._fw_loss.record(src_code, lost_frames, lost_bytes)
|
return self._fw_loss.record(src_code, lost_frames, lost_bytes) # type: ignore[no-any-return]
|
||||||
|
|
||||||
# -- Reset -------------------------------------------------------------------
|
# -- Reset -------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from src.backend.stats import UART_BITS_PER_BYTE
|
|||||||
|
|
||||||
|
|
||||||
def _format_speed(bps: float) -> str:
|
def _format_speed(bps: float) -> str:
|
||||||
return format_throughput(bps / UART_BITS_PER_BYTE)
|
return format_throughput(bps / UART_BITS_PER_BYTE) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
_SYNC_COLORS = {
|
_SYNC_COLORS = {
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ from tests.helpers import build_frame
|
|||||||
|
|
||||||
|
|
||||||
def _make_sum_frame(payload: bytes, src: int, sn: int) -> bytes:
|
def _make_sum_frame(payload: bytes, src: int, sn: int) -> bytes:
|
||||||
return build_frame(payload, src, sn, sum_checksum, checksum_scope_full=True)
|
return build_frame(payload, src, sn, sum_checksum, checksum_scope_full=True) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
def _make_xor_frame(payload: bytes, src: int, sn: int) -> bytes:
|
def _make_xor_frame(payload: bytes, src: int, sn: int) -> bytes:
|
||||||
return build_frame(payload, src, sn, xor_checksum, checksum_scope_full=True)
|
return build_frame(payload, src, sn, xor_checksum, checksum_scope_full=True) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
class TestFrameParserStateTransitions:
|
class TestFrameParserStateTransitions:
|
||||||
@@ -138,11 +138,11 @@ class TestFrameParserOutput:
|
|||||||
|
|
||||||
|
|
||||||
def _make_sum_header_only_frame(payload: bytes, src: int, sn: int) -> bytes:
|
def _make_sum_header_only_frame(payload: bytes, src: int, sn: int) -> bytes:
|
||||||
return build_frame(payload, src, sn, sum_checksum, checksum_scope_full=False)
|
return build_frame(payload, src, sn, sum_checksum, checksum_scope_full=False) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
def _make_xor_header_only_frame(payload: bytes, src: int, sn: int) -> bytes:
|
def _make_xor_header_only_frame(payload: bytes, src: int, sn: int) -> bytes:
|
||||||
return build_frame(payload, src, sn, xor_checksum, checksum_scope_full=False)
|
return build_frame(payload, src, sn, xor_checksum, checksum_scope_full=False) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
||||||
class TestChecksumAutoDetection:
|
class TestChecksumAutoDetection:
|
||||||
|
|||||||
@@ -7,18 +7,18 @@ from src.backend.models import LossType
|
|||||||
from src.backend.models import ThroughputInfo
|
from src.backend.models import ThroughputInfo
|
||||||
|
|
||||||
|
|
||||||
def test_frame_byte_count():
|
def test_frame_byte_count() -> None:
|
||||||
fbc = FrameByteCount(frames=100, bytes=5000)
|
fbc = FrameByteCount(frames=100, bytes=5000)
|
||||||
assert fbc.frames == 100
|
assert fbc.frames == 100
|
||||||
assert fbc.bytes == 5000
|
assert fbc.bytes == 5000
|
||||||
|
|
||||||
|
|
||||||
def test_loss_type_enum():
|
def test_loss_type_enum() -> None:
|
||||||
assert LossType.BUFFER == 'buffer'
|
assert LossType.BUFFER == 'buffer'
|
||||||
assert LossType.TRANSPORT == 'transport'
|
assert LossType.TRANSPORT == 'transport'
|
||||||
|
|
||||||
|
|
||||||
def test_funnel_snapshot_structure():
|
def test_funnel_snapshot_structure() -> None:
|
||||||
zero = FrameByteCount(frames=0, bytes=0)
|
zero = FrameByteCount(frames=0, bytes=0)
|
||||||
tp = ThroughputInfo(
|
tp = ThroughputInfo(
|
||||||
throughput_fps=0.0, throughput_bps=0.0, peak_write_frames=0, peak_write_bytes=0, peak_window_ms=10
|
throughput_fps=0.0, throughput_bps=0.0, peak_write_frames=0, peak_write_bytes=0, peak_window_ms=10
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
Verifies that reset("init") and reset("flush") dispatch correctly per the spec:
|
Verifies that reset("init") and reset("flush") dispatch correctly per the spec:
|
||||||
|
|
||||||
| Group | Components | INIT_DONE | FLUSH |
|
| Group | Components | INIT_DONE | FLUSH |
|
||||||
|------------------|---------------------------------------------|--------------------|------------------------------------|
|
|------------------|-----------------------------------------|--------------|------------------------------------|
|
||||||
| SN-coupled | SNGapTracker | full reset | full reset |
|
| SN-coupled | SNGapTracker | full reset | full reset |
|
||||||
| ENH_STAT-coupled | FirmwareLossTracker, FirmwareWrittenTracker | full reset | reset baselines, preserve accumulators |
|
| ENH_STAT-coupled | FirmwareLossTracker, FirmwareWritten | full reset | reset baselines, keep accumulators |
|
||||||
| Console-local | TransportMetrics, PeakBurstTracker, | preserve | preserve |
|
| Console-local | TransportMetrics, PeakBurstTracker, | preserve | preserve |
|
||||||
| | per_source_received, throughput cache | | |
|
| | per_source_received, throughput cache | | |
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from src.backend.stats import StatsAccumulator
|
from src.backend.stats import StatsAccumulator
|
||||||
|
|||||||
@@ -5,21 +5,21 @@ from src.backend.stats.sn_gap import SNGapTracker
|
|||||||
|
|
||||||
|
|
||||||
class TestSNGapTracker:
|
class TestSNGapTracker:
|
||||||
def setup_method(self):
|
def setup_method(self) -> None:
|
||||||
self.tracker = SNGapTracker()
|
self.tracker = SNGapTracker()
|
||||||
|
|
||||||
# --- Baseline ---
|
# --- Baseline ---
|
||||||
def test_first_frame_establishes_baseline(self):
|
def test_first_frame_establishes_baseline(self) -> None:
|
||||||
assert self.tracker.record(src_code=1, frame_sn=42) == 0
|
assert self.tracker.record(src_code=1, frame_sn=42) == 0
|
||||||
|
|
||||||
# --- In-order ---
|
# --- In-order ---
|
||||||
def test_sequential_no_gap(self):
|
def test_sequential_no_gap(self) -> None:
|
||||||
self.tracker.record(1, 0)
|
self.tracker.record(1, 0)
|
||||||
assert self.tracker.record(1, 1) == 0
|
assert self.tracker.record(1, 1) == 0
|
||||||
assert self.tracker.record(1, 2) == 0
|
assert self.tracker.record(1, 2) == 0
|
||||||
|
|
||||||
# --- Simple reorder (within window) ---
|
# --- Simple reorder (within window) ---
|
||||||
def test_reorder_no_false_gap(self):
|
def test_reorder_no_false_gap(self) -> None:
|
||||||
"""SN=8 arrives before SN=5,6,7 — no gaps should be counted."""
|
"""SN=8 arrives before SN=5,6,7 — no gaps should be counted."""
|
||||||
self.tracker.record(1, 5) # baseline → window_base=6
|
self.tracker.record(1, 5) # baseline → window_base=6
|
||||||
assert self.tracker.record(1, 8) == 0 # within window, NOT a gap
|
assert self.tracker.record(1, 8) == 0 # within window, NOT a gap
|
||||||
@@ -28,7 +28,7 @@ class TestSNGapTracker:
|
|||||||
assert self.tracker.totals().get(1, 0) == 0
|
assert self.tracker.totals().get(1, 0) == 0
|
||||||
|
|
||||||
# --- Confirmed loss ---
|
# --- Confirmed loss ---
|
||||||
def test_loss_confirmed_when_window_expires(self):
|
def test_loss_confirmed_when_window_expires(self) -> None:
|
||||||
"""Frame beyond window forces expiry of unreceived SNs."""
|
"""Frame beyond window forces expiry of unreceived SNs."""
|
||||||
self.tracker.record(1, 0) # baseline → base=1
|
self.tracker.record(1, 0) # baseline → base=1
|
||||||
# SN=1 never arrives; jump to SN=257 (beyond window of 256)
|
# SN=1 never arrives; jump to SN=257 (beyond window of 256)
|
||||||
@@ -37,13 +37,13 @@ class TestSNGapTracker:
|
|||||||
assert self.tracker.totals()[1] > 0
|
assert self.tracker.totals()[1] > 0
|
||||||
|
|
||||||
# --- Late arrival behind window ---
|
# --- Late arrival behind window ---
|
||||||
def test_late_arrival_ignored(self):
|
def test_late_arrival_ignored(self) -> None:
|
||||||
self.tracker.record(1, 0)
|
self.tracker.record(1, 0)
|
||||||
self.tracker.record(1, 257) # force window advance past 0
|
self.tracker.record(1, 257) # force window advance past 0
|
||||||
assert self.tracker.record(1, 1) == 0 # too late, ignored
|
assert self.tracker.record(1, 1) == 0 # too late, ignored
|
||||||
|
|
||||||
# --- Reset detection ---
|
# --- Reset detection ---
|
||||||
def test_large_backward_jump_resets_baseline(self):
|
def test_large_backward_jump_resets_baseline(self) -> None:
|
||||||
self.tracker.record(1, 1000)
|
self.tracker.record(1, 1000)
|
||||||
# SN jumps back to 5 (far beyond REORDER_WINDOW backward)
|
# SN jumps back to 5 (far beyond REORDER_WINDOW backward)
|
||||||
assert self.tracker.record(1, 5) == 0
|
assert self.tracker.record(1, 5) == 0
|
||||||
@@ -51,14 +51,14 @@ class TestSNGapTracker:
|
|||||||
assert self.tracker.record(1, 6) == 0
|
assert self.tracker.record(1, 6) == 0
|
||||||
|
|
||||||
# --- Multi-source independence ---
|
# --- Multi-source independence ---
|
||||||
def test_sources_independent(self):
|
def test_sources_independent(self) -> None:
|
||||||
self.tracker.record(1, 10)
|
self.tracker.record(1, 10)
|
||||||
self.tracker.record(2, 20)
|
self.tracker.record(2, 20)
|
||||||
assert self.tracker.record(1, 11) == 0
|
assert self.tracker.record(1, 11) == 0
|
||||||
assert self.tracker.record(2, 21) == 0
|
assert self.tracker.record(2, 21) == 0
|
||||||
|
|
||||||
# --- 24-bit wraparound ---
|
# --- 24-bit wraparound ---
|
||||||
def test_wraparound(self):
|
def test_wraparound(self) -> None:
|
||||||
SN_MAX = 1 << 24
|
SN_MAX = 1 << 24
|
||||||
self.tracker.record(1, SN_MAX - 2) # base = SN_MAX-1
|
self.tracker.record(1, SN_MAX - 2) # base = SN_MAX-1
|
||||||
assert self.tracker.record(1, SN_MAX - 1) == 0
|
assert self.tracker.record(1, SN_MAX - 1) == 0
|
||||||
@@ -66,13 +66,13 @@ class TestSNGapTracker:
|
|||||||
assert self.tracker.record(1, 1) == 0
|
assert self.tracker.record(1, 1) == 0
|
||||||
|
|
||||||
# --- Reset method ---
|
# --- Reset method ---
|
||||||
def test_reset_clears_all(self):
|
def test_reset_clears_all(self) -> None:
|
||||||
self.tracker.record(1, 10)
|
self.tracker.record(1, 10)
|
||||||
self.tracker.reset()
|
self.tracker.reset()
|
||||||
# After reset, next frame establishes new baseline
|
# After reset, next frame establishes new baseline
|
||||||
assert self.tracker.record(1, 0) == 0
|
assert self.tracker.record(1, 0) == 0
|
||||||
|
|
||||||
def test_reset_single_source(self):
|
def test_reset_single_source(self) -> None:
|
||||||
self.tracker.record(1, 10)
|
self.tracker.record(1, 10)
|
||||||
self.tracker.record(2, 20)
|
self.tracker.record(2, 20)
|
||||||
self.tracker.reset(src_code=1)
|
self.tracker.reset(src_code=1)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class TestStatsAccumulator:
|
|||||||
self, stats: StatsAccumulator, src_code: int, lost_frames: int, lost_bytes: int
|
self, stats: StatsAccumulator, src_code: int, lost_frames: int, lost_bytes: int
|
||||||
) -> tuple[int, int]:
|
) -> tuple[int, int]:
|
||||||
"""Helper: call record_enh_stat with dummy written/baudrate, return loss delta."""
|
"""Helper: call record_enh_stat with dummy written/baudrate, return loss delta."""
|
||||||
return stats.record_enh_stat(
|
return stats.record_enh_stat( # type: ignore[no-any-return]
|
||||||
src_code=src_code,
|
src_code=src_code,
|
||||||
written_frames=0,
|
written_frames=0,
|
||||||
lost_frames=lost_frames,
|
lost_frames=lost_frames,
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ _ZERO_TP = ThroughputInfo(
|
|||||||
|
|
||||||
|
|
||||||
def _snap(
|
def _snap(
|
||||||
src,
|
src: int,
|
||||||
produced=(0, 0),
|
produced: tuple[int, int] = (0, 0),
|
||||||
written=(0, 0),
|
written: tuple[int, int] = (0, 0),
|
||||||
received=(0, 0),
|
received: tuple[int, int] = (0, 0),
|
||||||
buf_loss=(0, 0),
|
buf_loss: tuple[int, int] = (0, 0),
|
||||||
tx_loss=(0, 0),
|
tx_loss: tuple[int, int] = (0, 0),
|
||||||
tp_fps=0.0,
|
tp_fps: float = 0.0,
|
||||||
peak_frames=0,
|
peak_frames: int = 0,
|
||||||
):
|
) -> FunnelSnapshot:
|
||||||
return FunnelSnapshot(
|
return FunnelSnapshot(
|
||||||
source=src,
|
source=src,
|
||||||
produced=FrameByteCount(*produced),
|
produced=FrameByteCount(*produced),
|
||||||
@@ -85,29 +85,29 @@ class TestFormatThroughput:
|
|||||||
|
|
||||||
|
|
||||||
class TestBuildFirmwareTable:
|
class TestBuildFirmwareTable:
|
||||||
def test_empty_returns_no_rows(self):
|
def test_empty_returns_no_rows(self) -> None:
|
||||||
table = _build_firmware_table([])
|
table = _build_firmware_table([])
|
||||||
assert table.row_count == 0
|
assert table.row_count == 0
|
||||||
|
|
||||||
def test_column_headers(self):
|
def test_column_headers(self) -> None:
|
||||||
table = _build_firmware_table([])
|
table = _build_firmware_table([])
|
||||||
headers = [str(col.header) for col in table.columns]
|
headers = [str(col.header) for col in table.columns]
|
||||||
assert 'Source' in headers
|
assert 'Source' in headers
|
||||||
assert any('Written' in h for h in headers)
|
assert any('Written' in h for h in headers)
|
||||||
assert any('Loss' in h for h in headers)
|
assert any('Loss' in h for h in headers)
|
||||||
|
|
||||||
def test_single_source(self):
|
def test_single_source(self) -> None:
|
||||||
snap = _snap(_SRC_HOST, written=(120, 6000))
|
snap = _snap(_SRC_HOST, written=(120, 6000))
|
||||||
table = _build_firmware_table([snap])
|
table = _build_firmware_table([snap])
|
||||||
assert table.row_count == 1
|
assert table.row_count == 1
|
||||||
assert len(table.columns) == 5
|
assert len(table.columns) == 5
|
||||||
|
|
||||||
def test_with_loss_shows_red(self):
|
def test_with_loss_shows_red(self) -> None:
|
||||||
snap = _snap(_SRC_HOST, written=(110, 5500), buf_loss=(10, 500))
|
snap = _snap(_SRC_HOST, written=(110, 5500), buf_loss=(10, 500))
|
||||||
table = _build_firmware_table([snap])
|
table = _build_firmware_table([snap])
|
||||||
assert table.row_count == 1
|
assert table.row_count == 1
|
||||||
|
|
||||||
def test_multiple_sources(self):
|
def test_multiple_sources(self) -> None:
|
||||||
snaps = [
|
snaps = [
|
||||||
_snap(_SRC_HOST, written=(100, 5000)),
|
_snap(_SRC_HOST, written=(100, 5000)),
|
||||||
_snap(_SRC_LL_TASK, written=(200, 10000)),
|
_snap(_SRC_LL_TASK, written=(200, 10000)),
|
||||||
@@ -117,11 +117,11 @@ class TestBuildFirmwareTable:
|
|||||||
|
|
||||||
|
|
||||||
class TestBuildConsoleTable:
|
class TestBuildConsoleTable:
|
||||||
def test_empty_returns_no_rows(self):
|
def test_empty_returns_no_rows(self) -> None:
|
||||||
table = _build_console_table([])
|
table = _build_console_table([])
|
||||||
assert table.row_count == 0
|
assert table.row_count == 0
|
||||||
|
|
||||||
def test_column_headers(self):
|
def test_column_headers(self) -> None:
|
||||||
table = _build_console_table([])
|
table = _build_console_table([])
|
||||||
headers = [str(col.header) for col in table.columns]
|
headers = [str(col.header) for col in table.columns]
|
||||||
assert 'Source' in headers
|
assert 'Source' in headers
|
||||||
@@ -129,13 +129,13 @@ class TestBuildConsoleTable:
|
|||||||
assert any('Average' in h for h in headers)
|
assert any('Average' in h for h in headers)
|
||||||
assert any('Peak' in h for h in headers)
|
assert any('Peak' in h for h in headers)
|
||||||
|
|
||||||
def test_single_source(self):
|
def test_single_source(self) -> None:
|
||||||
snap = _snap(_SRC_HOST, tp_fps=850.0, peak_frames=12)
|
snap = _snap(_SRC_HOST, tp_fps=850.0, peak_frames=12)
|
||||||
table = _build_console_table([snap])
|
table = _build_console_table([snap])
|
||||||
assert table.row_count == 1
|
assert table.row_count == 1
|
||||||
assert len(table.columns) == 7
|
assert len(table.columns) == 7
|
||||||
|
|
||||||
def test_zero_throughput_shows_dash(self):
|
def test_zero_throughput_shows_dash(self) -> None:
|
||||||
snap = _snap(_SRC_HOST, tp_fps=0.0, peak_frames=0)
|
snap = _snap(_SRC_HOST, tp_fps=0.0, peak_frames=0)
|
||||||
table = _build_console_table([snap])
|
table = _build_console_table([snap])
|
||||||
assert table.row_count == 1
|
assert table.row_count == 1
|
||||||
|
|||||||
Reference in New Issue
Block a user