blob: 07950388f4c75dd98342bc6c62184b8090e9aa63 [file]
"""
Property Test — Property 7: Generation script idempotency
Feature: consolidate-overrides-and-generation-script
**Validates: Requirements 11.4**
For any valid cluster configuration and the same full generated source
files, running the generation script twice SHALL produce byte-identical
output files.
"""
import os
import re
import sys
import tempfile
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st
# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
FULL_CLUSTER_OBJECTS = os.path.join(
REPO_ROOT, "zzz_generated", "app-common", "app-common",
"zap-generated", "cluster-objects.cpp",
)
FULL_ATTR_DECODER = os.path.join(
REPO_ROOT, "src", "controller", "java", "zap-generated",
"CHIPAttributeTLVValueDecoder.cpp",
)
FULL_EVENT_DECODER = os.path.join(
REPO_ROOT, "src", "controller", "java", "zap-generated",
"CHIPEventTLVValueDecoder.cpp",
)
FULL_ACCESSORS = os.path.join(
REPO_ROOT, "zzz_generated", "app-common", "app-common",
"zap-generated", "attributes", "Accessors.cpp",
)
# Import the generation script
sys.path.insert(0, os.path.join(REPO_ROOT, "examples", "tv-casting-app", "tv-casting-common"))
from generate_slim_overrides import (generate_accessors, generate_cluster_objects, generate_cluster_servers_gni, # noqa: E402
generate_tlv_decoder)
# ---------------------------------------------------------------------------
# Discover cluster names from full sources
# ---------------------------------------------------------------------------
def _discover_cluster_names(source_path):
names = set()
with open(source_path, "r") as f:
for line in f:
m = re.match(r'#include\s+<clusters/([^/]+)/', line)
if m and m.group(1) != "shared":
names.add(m.group(1))
return sorted(names)
def _discover_tlv_cluster_names(source_path):
names = set()
with open(source_path, "r") as f:
for line in f:
m = re.search(r'case\s+app::Clusters::(\w+)::Id:', line)
if m:
names.add(m.group(1))
return sorted(names)
ALL_OBJ_CLUSTERS = _discover_cluster_names(FULL_CLUSTER_OBJECTS)
ALL_ATTR_CLUSTERS = _discover_tlv_cluster_names(FULL_ATTR_DECODER)
ALL_EVENT_CLUSTERS = _discover_tlv_cluster_names(FULL_EVENT_DECODER)
SAMPLE_SERVERS = [
"access-control-server", "basic-information", "bindings",
"descriptor", "groups-server", "identify-server",
]
# ---------------------------------------------------------------------------
# Strategies
# ---------------------------------------------------------------------------
obj_cluster_subsets = st.lists(
st.sampled_from(ALL_OBJ_CLUSTERS),
min_size=1,
max_size=min(len(ALL_OBJ_CLUSTERS), 20),
unique=True,
)
attr_cluster_subsets = st.lists(
st.sampled_from(ALL_ATTR_CLUSTERS),
min_size=1,
max_size=min(len(ALL_ATTR_CLUSTERS), 20),
unique=True,
)
event_cluster_subsets = st.lists(
st.sampled_from(ALL_EVENT_CLUSTERS),
min_size=1,
max_size=min(len(ALL_EVENT_CLUSTERS), 20),
unique=True,
)
server_subsets = st.lists(
st.sampled_from(SAMPLE_SERVERS),
min_size=1,
max_size=len(SAMPLE_SERVERS),
unique=True,
)
# ---------------------------------------------------------------------------
# Property tests
# ---------------------------------------------------------------------------
@given(subset=obj_cluster_subsets)
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.too_slow],
deadline=None,
)
def test_cluster_objects_idempotent(subset):
"""
**Validates: Requirements 11.4**
Running generate_cluster_objects twice with the same inputs
SHALL produce byte-identical output.
"""
with tempfile.TemporaryDirectory() as tmpdir:
path1 = os.path.join(tmpdir, "run1.cpp")
path2 = os.path.join(tmpdir, "run2.cpp")
generate_cluster_objects(FULL_CLUSTER_OBJECTS, subset, path1)
generate_cluster_objects(FULL_CLUSTER_OBJECTS, subset, path2)
with open(path1, "rb") as f1, open(path2, "rb") as f2:
assert f1.read() == f2.read(), (
"cluster-objects output is not idempotent"
)
@given(subset=attr_cluster_subsets)
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.too_slow],
deadline=None,
)
def test_attr_decoder_idempotent(subset):
"""
**Validates: Requirements 11.4**
Running generate_tlv_decoder (attribute) twice with the same inputs
SHALL produce byte-identical output.
"""
with tempfile.TemporaryDirectory() as tmpdir:
path1 = os.path.join(tmpdir, "run1.cpp")
path2 = os.path.join(tmpdir, "run2.cpp")
generate_tlv_decoder(FULL_ATTR_DECODER, subset, path1, "attribute")
generate_tlv_decoder(FULL_ATTR_DECODER, subset, path2, "attribute")
with open(path1, "rb") as f1, open(path2, "rb") as f2:
assert f1.read() == f2.read(), (
"attribute TLV decoder output is not idempotent"
)
@given(subset=event_cluster_subsets)
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.too_slow],
deadline=None,
)
def test_event_decoder_idempotent(subset):
"""
**Validates: Requirements 11.4**
Running generate_tlv_decoder (event) twice with the same inputs
SHALL produce byte-identical output.
"""
with tempfile.TemporaryDirectory() as tmpdir:
path1 = os.path.join(tmpdir, "run1.cpp")
path2 = os.path.join(tmpdir, "run2.cpp")
generate_tlv_decoder(FULL_EVENT_DECODER, subset, path1, "event")
generate_tlv_decoder(FULL_EVENT_DECODER, subset, path2, "event")
with open(path1, "rb") as f1, open(path2, "rb") as f2:
assert f1.read() == f2.read(), (
"event TLV decoder output is not idempotent"
)
@given(subset=obj_cluster_subsets)
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.too_slow],
deadline=None,
)
def test_accessors_idempotent(subset):
"""
**Validates: Requirements 11.4**
Running generate_accessors twice with the same inputs
SHALL produce byte-identical output.
"""
with tempfile.TemporaryDirectory() as tmpdir:
path1 = os.path.join(tmpdir, "run1.cpp")
path2 = os.path.join(tmpdir, "run2.cpp")
generate_accessors(FULL_ACCESSORS, subset, path1)
generate_accessors(FULL_ACCESSORS, subset, path2)
with open(path1, "rb") as f1, open(path2, "rb") as f2:
assert f1.read() == f2.read(), (
"Accessors output is not idempotent"
)
@given(servers=server_subsets)
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.too_slow],
deadline=None,
)
def test_cluster_servers_gni_idempotent(servers):
"""
**Validates: Requirements 11.4**
Running generate_cluster_servers_gni twice with the same inputs
SHALL produce byte-identical output.
"""
with tempfile.TemporaryDirectory() as tmpdir:
path1 = os.path.join(tmpdir, "run1.gni")
path2 = os.path.join(tmpdir, "run2.gni")
generate_cluster_servers_gni(servers, path1)
generate_cluster_servers_gni(servers, path2)
with open(path1, "rb") as f1, open(path2, "rb") as f2:
assert f1.read() == f2.read(), (
"cluster-servers.gni output is not idempotent"
)