[api] Gate the bluetooth connection messages on their own define (#18281)

This commit is contained in:
J. Nick Koston
2026-08-11 21:31:13 -05:00
committed by GitHub
parent 9605b34c69
commit 37eae9b466
22 changed files with 238 additions and 128 deletions
@@ -3,7 +3,7 @@ from tests.testing_helpers import ComponentManifestOverride
def override_manifest(manifest: ComponentManifestOverride) -> None:
# close_service_batch compiles only under BLUETOOTH_CONNECTION_HAS_GATT;
# close_service_batch compiles only under USE_BLUETOOTH_PROXY_CONNECTIONS;
# emit the backend define so the host build exercises it.
async def to_code_testing(config):
# These defines are global to the merged host test binary; safe
@@ -11,6 +11,9 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
cg.add_define("USE_BLE_GATT_CLIENT")
cg.add_define("USE_BLE_GATT_CLIENT_STUB_BACKEND")
cg.add_define("USE_BLUETOOTH_PROXY")
# Gates the connection half of the API surface, which is what
# close_service_batch and the GATT response types live behind.
cg.add_define("USE_BLUETOOTH_PROXY_CONNECTIONS")
cg.add_define("BLUETOOTH_PROXY_ADVERTISEMENT_BATCH_SIZE", 16)
cg.add_define("BLUETOOTH_PROXY_MAX_CONNECTIONS", 1)
@@ -0,0 +1,93 @@
"""Unit tests for script/api_protobuf/api_protobuf.py generator logic.
ci-api-proto.yml only checks that the committed output matches what the
generator currently produces, so a semantic regression in the generator would
be committed and matched without anything failing. These tests pin the
semantics directly.
"""
from __future__ import annotations
from pathlib import Path
import sys
import pytest
sys.path.insert(0, str(Path(__file__).parents[4] / "script" / "api_protobuf"))
from api_protobuf import _make_ifdef_line, get_varint64_ifdef # noqa: E402
from google.protobuf import descriptor_pb2 # noqa: E402
def _file_with_messages(
*messages: tuple[str, int, bool],
) -> descriptor_pb2.FileDescriptorProto:
"""Build a FileDescriptorProto with one single-field message per entry.
Each entry is (message_name, field_type, deprecated).
"""
file_desc = descriptor_pb2.FileDescriptorProto(name="test.proto")
for name, field_type, deprecated in messages:
msg = file_desc.message_type.add(name=name)
field = msg.field.add(name="value", number=1, type=field_type)
field.options.deprecated = deprecated
return file_desc
UINT64 = descriptor_pb2.FieldDescriptorProto.TYPE_UINT64
INT64 = descriptor_pb2.FieldDescriptorProto.TYPE_INT64
SINT64 = descriptor_pb2.FieldDescriptorProto.TYPE_SINT64
UINT32 = descriptor_pb2.FieldDescriptorProto.TYPE_UINT32
FIXED64 = descriptor_pb2.FieldDescriptorProto.TYPE_FIXED64
def test_no_varint64_fields() -> None:
file_desc = _file_with_messages(("A", UINT32, False), ("B", FIXED64, False))
assert get_varint64_ifdef(file_desc, {}) == (False, None)
@pytest.mark.parametrize("field_type", [UINT64, INT64, SINT64])
def test_single_guard_is_kept(field_type: int) -> None:
file_desc = _file_with_messages(("A", field_type, False))
assert get_varint64_ifdef(file_desc, {"A": "USE_X"}) == (True, "USE_X")
def test_two_guards_emit_the_union() -> None:
# The regression this pins: multiple guards used to collapse to
# unconditional, pulling 64-bit varint support into unrelated builds.
file_desc = _file_with_messages(("A", UINT64, False), ("B", INT64, False))
guards = {"A": "USE_X", "B": "USE_Y"}
assert get_varint64_ifdef(file_desc, guards) == (True, "USE_X || USE_Y")
def test_union_is_sorted_for_deterministic_output() -> None:
file_desc = _file_with_messages(("B", UINT64, False), ("A", INT64, False))
guards = {"B": "USE_Y", "A": "USE_X"}
assert get_varint64_ifdef(file_desc, guards) == (True, "USE_X || USE_Y")
def test_any_unconditional_message_wins() -> None:
file_desc = _file_with_messages(("A", UINT64, False), ("B", INT64, False))
assert get_varint64_ifdef(file_desc, {"A": "USE_X"}) == (True, None)
def test_deprecated_fields_and_messages_are_ignored() -> None:
file_desc = _file_with_messages(("A", UINT64, True), ("B", INT64, False))
file_desc.message_type[1].options.deprecated = True
assert get_varint64_ifdef(file_desc, {"A": "USE_X", "B": "USE_Y"}) == (False, None)
def test_make_ifdef_line_simple_identifier() -> None:
assert _make_ifdef_line("USE_X") == "#ifdef USE_X"
def test_make_ifdef_line_union_wraps_each_identifier() -> None:
# The second half of the varint64 union guard: compound conditions must
# become #if defined(A) || defined(B), never #ifdef of the raw string.
assert _make_ifdef_line("USE_X || USE_Y") == "#if defined(USE_X) || defined(USE_Y)"
def test_make_ifdef_line_conjunction_and_negation() -> None:
assert (
_make_ifdef_line("USE_X && !USE_Y") == "#if defined(USE_X) && !defined(USE_Y)"
)