From e48dc7139b28c4d620125b3bcf589e344296c6d7 Mon Sep 17 00:00:00 2001 From: "Zhibin (Ryan) Wen" Date: Tue, 31 Mar 2026 15:23:05 +0800 Subject: [PATCH] components/esp_matter/test: add jsontlv roundtrip and validation tests Signed-off-by: Zhibin (Ryan) Wen --- components/esp_matter/test/CMakeLists.txt | 1 + components/esp_matter/test/jsontlv.cpp | 149 ++++++++++++++++++ .../unit_test_app/pytest_unit_test_app.py | 7 + 3 files changed, 157 insertions(+) create mode 100644 components/esp_matter/test/jsontlv.cpp diff --git a/components/esp_matter/test/CMakeLists.txt b/components/esp_matter/test/CMakeLists.txt index 671198715..160cd97cb 100644 --- a/components/esp_matter/test/CMakeLists.txt +++ b/components/esp_matter/test/CMakeLists.txt @@ -1,6 +1,7 @@ list(APPEND srcs_list "attribute_get_val.cpp") list(APPEND srcs_list "attribute_get_val_type.cpp") list(APPEND srcs_list "attribute_report.cpp") +list(APPEND srcs_list "jsontlv.cpp") idf_component_register(SRCS ${srcs_list} INCLUDE_DIRS "." diff --git a/components/esp_matter/test/jsontlv.cpp b/components/esp_matter/test/jsontlv.cpp new file mode 100644 index 000000000..f6e450b17 --- /dev/null +++ b/components/esp_matter/test/jsontlv.cpp @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +static constexpr size_t k_tlv_buffer_size = 1024; + +static esp_err_t roundtrip_json_tree(const char *input_json, cJSON **output_json) +{ + if (output_json == nullptr) { + return ESP_ERR_INVALID_ARG; + } + + *output_json = nullptr; + + uint8_t buffer[k_tlv_buffer_size] = { 0 }; + chip::TLV::TLVWriter writer; + writer.Init(buffer, sizeof(buffer)); + + esp_err_t err = esp_matter::json_to_tlv(input_json, writer, chip::TLV::AnonymousTag()); + if (err != ESP_OK) { + return err; + } + + chip::TLV::TLVReader reader; + reader.Init(buffer, writer.GetLengthWritten()); + return esp_matter::tlv_to_json(reader, output_json); +} + +static void expect_roundtrip(const char *input_json, const char *expected_json) +{ + cJSON *actual_json = nullptr; + cJSON *expected_json_tree = cJSON_Parse(expected_json); + TEST_ASSERT_NOT_NULL(expected_json_tree); + + esp_err_t err = roundtrip_json_tree(input_json, &actual_json); + TEST_ASSERT_EQUAL(ESP_OK, err); + TEST_ASSERT_NOT_NULL(actual_json); + + char *actual_printed = cJSON_PrintUnformatted(actual_json); + char *expected_printed = cJSON_PrintUnformatted(expected_json_tree); + TEST_ASSERT_NOT_NULL(actual_printed); + TEST_ASSERT_NOT_NULL(expected_printed); + TEST_ASSERT_EQUAL_STRING(expected_printed, actual_printed); + + cJSON_free(actual_printed); + cJSON_free(expected_printed); + cJSON_Delete(actual_json); + cJSON_Delete(expected_json_tree); +} + +static void expect_json_to_tlv_failure(const char *input_json) +{ + uint8_t buffer[k_tlv_buffer_size] = { 0 }; + chip::TLV::TLVWriter writer; + writer.Init(buffer, sizeof(buffer)); + + esp_err_t err = esp_matter::json_to_tlv(input_json, writer, chip::TLV::AnonymousTag()); + TEST_ASSERT_TRUE(err != ESP_OK); +} + +TEST_CASE("jsontlv roundtrip scalar values", "[jsontlv][roundtrip]") +{ + expect_roundtrip( + R"({"5:STR":"chip","2:I16":-1234,"4:BOOL":true,"1:U8":42,"3:NULL":null})", + R"({"1:U8":42,"2:I16":-1234,"3:NULL":null,"4:BOOL":true,"5:STR":"chip"})"); + + expect_roundtrip( + R"({"2:OBJ":{"4:BOOL":false,"1:U8":7},"1:ARR-U8":[3,1,2]})", + R"({"1:ARR-U8":[3,1,2],"2:OBJ":{"1:U8":7,"4:BOOL":false}})"); + + expect_roundtrip( + R"({"1:BYT":"AQID","2:FP":"INF","3:DFP":"-INF"})", + R"({"1:BYT":"AQID","2:FP":"INF","3:DFP":"-INF"})"); + + expect_roundtrip( + R"({"1:I64":"-1234567890123456789","2:U64":"12345678901234567890"})", + R"({"1:I64":"-1234567890123456789","2:U64":"12345678901234567890"})"); +} + +TEST_CASE("jsontlv roundtrip container and ordering cases", "[jsontlv][roundtrip]") +{ + expect_roundtrip( + R"({"1:ARR-?":[]})", + R"({"1:ARR-?":[]})"); + + expect_roundtrip( + R"({"255:U16":65535,"1:U8":1})", + R"({"1:U8":1,"255:U16":65535})"); + + expect_roundtrip( + R"({"9:OBJ":{"3:STR":"c","1:STR":"a","2:STR":"b"},"2:U8":2,"1:U8":1})", + R"({"1:U8":1,"2:U8":2,"9:OBJ":{"1:STR":"a","2:STR":"b","3:STR":"c"}})"); + + expect_roundtrip( + R"({"7:ARR-STR":[]})", + R"({"7:ARR-?":[]})"); +} + +TEST_CASE("jsontlv roundtrip numeric float values", "[jsontlv][roundtrip]") +{ + cJSON *json = nullptr; + esp_err_t err = roundtrip_json_tree(R"({"1:FP":1.5,"2:DFP":-2.25})", &json); + TEST_ASSERT_EQUAL(ESP_OK, err); + TEST_ASSERT_NOT_NULL(json); + + cJSON *float_value = cJSON_GetObjectItemCaseSensitive(json, "1:FP"); + TEST_ASSERT_NOT_NULL(float_value); + TEST_ASSERT_TRUE(cJSON_IsNumber(float_value)); + TEST_ASSERT_DOUBLE_WITHIN(0.0001, 1.5, float_value->valuedouble); + + cJSON *double_value = cJSON_GetObjectItemCaseSensitive(json, "2:DFP"); + TEST_ASSERT_NOT_NULL(double_value); + TEST_ASSERT_TRUE(cJSON_IsNumber(double_value)); + TEST_ASSERT_DOUBLE_WITHIN(0.0001, -2.25, double_value->valuedouble); + + cJSON_Delete(json); +} + +TEST_CASE("jsontlv rejects invalid structure and type inputs", "[jsontlv][invalid]") +{ + expect_json_to_tlv_failure(R"({"1:ARR":[1,2,3]})"); + expect_json_to_tlv_failure(R"({"1:BYT":"not-base64"})"); + expect_json_to_tlv_failure(R"({"1":42})"); + expect_json_to_tlv_failure(R"({"1:BOOL":"true"})"); + expect_json_to_tlv_failure(R"({"1:FP":"NaN"})"); + expect_json_to_tlv_failure(R"({"1:I64":"123aaa"})"); + expect_json_to_tlv_failure(R"({"1:I64":"aaa123"})"); + expect_json_to_tlv_failure(R"({"1:U64":"123aaa"})"); + expect_json_to_tlv_failure(R"({"1:U64":"aaa123"})"); +} + +TEST_CASE("jsontlv rejects invalid numeric boundaries", "[jsontlv][invalid]") +{ + expect_json_to_tlv_failure(R"({"1:U8":256})"); + expect_json_to_tlv_failure(R"({"1:U32":-1})"); + expect_json_to_tlv_failure(R"({"1:I8":128})"); + expect_json_to_tlv_failure(R"({"4294967295:U8":1})"); + expect_json_to_tlv_failure(R"({"1:U32":1.5})"); + expect_json_to_tlv_failure(R"({"1:I32":2147483648})"); +} diff --git a/examples/unit_test_app/pytest_unit_test_app.py b/examples/unit_test_app/pytest_unit_test_app.py index 59df44f5b..4781fa730 100644 --- a/examples/unit_test_app/pytest_unit_test_app.py +++ b/examples/unit_test_app/pytest_unit_test_app.py @@ -42,3 +42,10 @@ def test_get_val_type(dut: QemuDut) -> None: def test_update_report(dut: QemuDut) -> None: run_group(dut, 'report') run_group(dut, 'update') + + +@pytest.mark.host_test +@pytest.mark.qemu +@pytest.mark.esp32c3 +def test_jsontlv(dut: QemuDut) -> None: + run_group(dut, 'jsontlv')