| """ | |
| 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) |