blob: 79110d1b54a14ec65d06c70e86a6a045f4e5b755 [file]
"""
Property Test — Property 3: Interface compatibility (attribute & event decoders)
**Validates: Requirements 1.3, 2.3**
This test parses both the slim attribute decoder
(`CHIPAttributeTLVValueDecoder-override.cpp`) and the full zap-generated
decoder (`zap-generated/CHIPAttributeTLVValueDecoder.cpp`), as well as
the slim event decoder (`CHIPEventTLVValueDecoder-override.cpp`) and the
full zap-generated event decoder (`zap-generated/CHIPEventTLVValueDecoder.cpp`),
and verifies:
1. The set of `#include` directives matches between slim and full (for each).
2. The function signature matches between slim and full (for each).
Feature: casting-client-cluster-reduction
Property 3: Interface compatibility
"""
import os
import re
from pathlib import Path
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
# Paths
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
REPO_ROOT = next(filter(lambda p: (p / 'SPECIFICATION_VERSION').is_file(), Path(__file__).parents))
SLIM_ATTR_DECODER = os.path.join(
REPO_ROOT,
"examples",
"tv-casting-app",
"tv-casting-common",
"CHIPAttributeTLVValueDecoder-override.cpp",
)
FULL_ATTR_DECODER = os.path.join(
REPO_ROOT,
"src",
"controller",
"java",
"zap-generated",
"CHIPAttributeTLVValueDecoder.cpp",
)
SLIM_EVENT_DECODER = os.path.join(
REPO_ROOT,
"examples",
"tv-casting-app",
"tv-casting-common",
"CHIPEventTLVValueDecoder-override.cpp",
)
FULL_EVENT_DECODER = os.path.join(
REPO_ROOT,
"src",
"controller",
"java",
"zap-generated",
"CHIPEventTLVValueDecoder.cpp",
)
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
# Helpers
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
def _read_file(path: str) -> str:
with open(path, "r") as f:
return f.read()
def _extract_includes(content: str) -> set:
"""
Extract all ``#include`` directives from a C++ source file.
Returns a set of the raw include strings, e.g.
``{'<jni.h>', '<controller/java/CHIPAttributeTLVValueDecoder.h>'}``.
Angle-bracket and quoted includes are both captured.
"""
pattern = r'^\s*#include\s+([<"][^>"]+[>"])'
return set(re.findall(pattern, content, re.MULTILINE))
def _extract_function_signature(content: str, func_name: str) -> str:
"""
Extract the function signature line for *func_name* from C++ source.
The signature is normalised: leading/trailing whitespace stripped,
internal runs of whitespace collapsed to a single space, and the
opening brace (if on the same line) removed. This makes comparison
resilient to minor formatting differences.
"""
# Match the return type + function name + params, possibly spanning
# multiple lines up to the opening brace.
pattern = (
r"((?:jobject)\s+"
+ re.escape(func_name)
+ r"\s*\([^)]*\))"
)
m = re.search(pattern, content, re.DOTALL)
if not m:
return ""
sig = m.group(1)
# Normalise whitespace
sig = re.sub(r"\s+", " ", sig).strip()
return sig
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
# Property - based tests
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
@given(dummy=st.just(True))
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.function_scoped_fixture],
deadline=None,
)
def test_attribute_decoder_includes_match(dummy):
"""
**Validates: Requirements 1.3, 2.3**
Property 3: The set of #include directives in the slim attribute
decoder SHALL match those in the full zap-generated attribute decoder.
"""
slim_content = _read_file(SLIM_ATTR_DECODER)
full_content = _read_file(FULL_ATTR_DECODER)
slim_includes = _extract_includes(slim_content)
full_includes = _extract_includes(full_content)
assert slim_includes == full_includes, (
f"Include set mismatch between slim and full attribute decoders.\n"
f" Only in slim: {sorted(slim_includes - full_includes)}\n"
f" Only in full: {sorted(full_includes - slim_includes)}\n"
f" Slim includes: {sorted(slim_includes)}\n"
f" Full includes: {sorted(full_includes)}"
)
@given(dummy=st.just(True))
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.function_scoped_fixture],
deadline=None,
)
def test_attribute_decoder_function_signature_matches(dummy):
"""
**Validates: Requirements 1.3, 2.3**
Property 3: The DecodeAttributeValue() function signature in the slim
attribute decoder SHALL match the signature in the full zap-generated
attribute decoder, ensuring link-time compatibility with the JNI bridge.
"""
slim_content = _read_file(SLIM_ATTR_DECODER)
full_content = _read_file(FULL_ATTR_DECODER)
slim_sig = _extract_function_signature(slim_content, "DecodeAttributeValue")
full_sig = _extract_function_signature(full_content, "DecodeAttributeValue")
assert slim_sig, (
"Could not find DecodeAttributeValue() signature in slim decoder."
)
assert full_sig, (
"Could not find DecodeAttributeValue() signature in full decoder."
)
assert slim_sig == full_sig, (
f"Function signature mismatch.\n"
f" Slim: {slim_sig}\n"
f" Full: {full_sig}"
)
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
# Property - based tests — Event decoder
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
@given(dummy=st.just(True))
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.function_scoped_fixture],
deadline=None,
)
def test_event_decoder_includes_match(dummy):
"""
**Validates: Requirements 2.3**
Property 3: The set of #include directives in the slim event
decoder SHALL match those in the full zap-generated event decoder.
"""
slim_content = _read_file(SLIM_EVENT_DECODER)
full_content = _read_file(FULL_EVENT_DECODER)
slim_includes = _extract_includes(slim_content)
full_includes = _extract_includes(full_content)
assert slim_includes == full_includes, (
f"Include set mismatch between slim and full event decoders.\n"
f" Only in slim: {sorted(slim_includes - full_includes)}\n"
f" Only in full: {sorted(full_includes - slim_includes)}\n"
f" Slim includes: {sorted(slim_includes)}\n"
f" Full includes: {sorted(full_includes)}"
)
@given(dummy=st.just(True))
@settings(
max_examples=100,
suppress_health_check=[HealthCheck.function_scoped_fixture],
deadline=None,
)
def test_event_decoder_function_signature_matches(dummy):
"""
**Validates: Requirements 2.3**
Property 3: The DecodeEventValue() function signature in the slim
event decoder SHALL match the signature in the full zap-generated
event decoder, ensuring link-time compatibility with the JNI bridge.
"""
slim_content = _read_file(SLIM_EVENT_DECODER)
full_content = _read_file(FULL_EVENT_DECODER)
slim_sig = _extract_function_signature(slim_content, "DecodeEventValue")
full_sig = _extract_function_signature(full_content, "DecodeEventValue")
assert slim_sig, (
"Could not find DecodeEventValue() signature in slim event decoder."
)
assert full_sig, (
"Could not find DecodeEventValue() signature in full event decoder."
)
assert slim_sig == full_sig, (
f"Function signature mismatch.\n"
f" Slim: {slim_sig}\n"
f" Full: {full_sig}"
)
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
# Allow running directly
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
if __name__ == "__main__":
import sys
print("Running interface compatibility property tests (attribute & event decoders)...")
tests = [
("3a: Attribute include sets match", test_attribute_decoder_includes_match),
("3b: Attribute function signature matches", test_attribute_decoder_function_signature_matches),
("3c: Event include sets match", test_event_decoder_includes_match),
("3d: Event function signature matches", test_event_decoder_function_signature_matches),
]
all_passed = True
for name, test_fn in tests:
try:
test_fn()
print(f" PASS: {name}")
except AssertionError as e:
print(f" FAIL: {name}\n {e}")
all_passed = False
except Exception as e:
print(f" ERROR: {name}\n {e}")
all_passed = False
sys.exit(0 if all_passed else 1)