mirror of
https://github.com/espressif/esp-matter.git
synced 2026-04-27 19:13:13 +00:00
Merge branch 'unit-test-app' into 'main'
unit tests framework with QEMU CI See merge request app-frameworks/esp-matter!1321
This commit is contained in:
@@ -12,3 +12,4 @@ _build/
|
||||
tools/chip-tool/
|
||||
.zap/
|
||||
.DS_Store
|
||||
pytest_embedded_log
|
||||
|
||||
@@ -612,6 +612,61 @@ pytest_esp32h2_esp_matter_dut:
|
||||
fi
|
||||
tags: ["esp32h2", "esp_matter_dut"]
|
||||
|
||||
build_unit_test_app_qemu:
|
||||
extends:
|
||||
- .build_examples_template
|
||||
artifacts:
|
||||
paths:
|
||||
- "examples/unit_test_app/build/*.bin"
|
||||
- "examples/unit_test_app/build/flasher_args.json"
|
||||
- "examples/unit_test_app/build/config/sdkconfig.json"
|
||||
- "examples/unit_test_app/build/bootloader/*.bin"
|
||||
- "examples/unit_test_app/build/partition_table/*.bin"
|
||||
- "examples/unit_test_app/build/build_log.txt"
|
||||
when: always
|
||||
expire_in: 4 days
|
||||
script:
|
||||
- cd ${ESP_MATTER_PATH}/examples/unit_test_app
|
||||
- idf.py set-target esp32c3 build
|
||||
|
||||
pytest_unit_test_app_qemu:
|
||||
stage: target_test
|
||||
image: ${DOCKER_IMAGE_NAME}:chip_${CHIP_SHORT_HASH}_idf_${IDF_CHECKOUT_REF}
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "main" || $CI_PIPELINE_SOURCE == "push"
|
||||
needs:
|
||||
- build_unit_test_app_qemu
|
||||
before_script:
|
||||
- *add_gitlab_ssh_key
|
||||
- *get_build_caches
|
||||
- *setup_idf
|
||||
- cd ${ESP_MATTER_PATH}
|
||||
- mkdir -p ${REPOS_PATH}
|
||||
- *setup_matter
|
||||
- *update_build_caches
|
||||
variables:
|
||||
REPOS_PATH: "$CI_PROJECT_DIR/repos"
|
||||
script:
|
||||
- cd ${ESP_MATTER_PATH}
|
||||
- apt-get update && apt-get install -y -q libslirp0
|
||||
- python ${IDF_PATH}/tools/idf_tools.py install qemu-riscv32
|
||||
- eval "$(python ${IDF_PATH}/tools/idf_tools.py export)"
|
||||
- pip install -r tools/ci/requirements-pytest.txt
|
||||
- pytest examples/unit_test_app/pytest_unit_test_app.py
|
||||
--target esp32c3
|
||||
-m qemu
|
||||
--embedded-services idf,qemu
|
||||
--junitxml=XUNIT_RESULT.xml
|
||||
--qemu-extra-args="-global driver=timer.esp32c3.timg,property=wdt_disable,value=true"
|
||||
artifacts:
|
||||
paths:
|
||||
- "pytest_embedded_log/"
|
||||
reports:
|
||||
junit: XUNIT_RESULT.xml
|
||||
when: always
|
||||
expire_in: 4 days
|
||||
tags: ["host_test"]
|
||||
|
||||
build_upstream_examples:
|
||||
resource_group: build_upstream_examples
|
||||
extends:
|
||||
|
||||
@@ -0,0 +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")
|
||||
|
||||
idf_component_register(SRCS ${srcs_list}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES unity esp_matter)
|
||||
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <unity.h>
|
||||
#include <esp_matter.h>
|
||||
#include <esp_matter_core.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
using namespace esp_matter;
|
||||
using namespace chip::app::Clusters;
|
||||
|
||||
static node_t *test_node = nullptr;
|
||||
static endpoint_t *root_endpoint = nullptr;
|
||||
static endpoint_t *test_endpoint = nullptr;
|
||||
static uint16_t test_endpoint_id = 0;
|
||||
static uint16_t root_endpoint_id = 0;
|
||||
|
||||
// Configurable test values for ESP Matter managed attributes
|
||||
static uint8_t test_current_level = 42;
|
||||
static bool test_on_off = true;
|
||||
static uint16_t test_identify_time = 10;
|
||||
static uint8_t test_identify_type = 2;
|
||||
static uint8_t test_color_mode = 1;
|
||||
static uint16_t test_color_temp = 250;
|
||||
|
||||
void setup_for_get_val()
|
||||
{
|
||||
static bool setup_done = false;
|
||||
if (setup_done) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_flash_init();
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
node::config_t node_config;
|
||||
test_node = node::create(&node_config, nullptr, nullptr);
|
||||
TEST_ASSERT_NOT_NULL(test_node);
|
||||
|
||||
root_endpoint = endpoint::get(test_node, 0);
|
||||
TEST_ASSERT_NOT_NULL(root_endpoint);
|
||||
|
||||
root_endpoint_id = endpoint::get_id(root_endpoint);
|
||||
TEST_ASSERT_EQUAL(0, root_endpoint_id);
|
||||
|
||||
// basically we would need some dataset that we should use for the test
|
||||
// for now we are using the extended_color_light::config_t
|
||||
endpoint::extended_color_light::config_t light_config;
|
||||
light_config.on_off.on_off = test_on_off;
|
||||
light_config.level_control.current_level = nullable<uint8_t>(test_current_level);
|
||||
light_config.identify.identify_time = test_identify_time;
|
||||
light_config.identify.identify_type = test_identify_type;
|
||||
light_config.color_control.color_mode = test_color_mode;
|
||||
test_endpoint = endpoint::extended_color_light::create(test_node, &light_config, ENDPOINT_FLAG_NONE, nullptr);
|
||||
TEST_ASSERT_NOT_NULL(test_endpoint);
|
||||
|
||||
test_endpoint_id = endpoint::get_id(test_endpoint);
|
||||
err = esp_matter::start(nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
setup_done = true;
|
||||
}
|
||||
|
||||
void teardown_for_get_val()
|
||||
{
|
||||
// TODO: Add proper cleanup once the Matter stack supports a clean shutdown sequence
|
||||
return;
|
||||
}
|
||||
|
||||
TEST_CASE("get_val invalid inputs", "[get_val][invalid]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_err_t err = attribute::get_val(nullptr, nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
err = attribute::get_val(nullptr, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
attribute_t *attr = attribute::get(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
err = attribute::get_val(attr, nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
/// with ids as input
|
||||
err = attribute::get_val(chip::kInvalidEndpointId, chip::kInvalidClusterId, chip::kInvalidAttributeId, nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
err = attribute::get_val(chip::kInvalidEndpointId, chip::kInvalidClusterId, chip::kInvalidAttributeId, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// Arrays - not supported for get_val
|
||||
TEST_CASE("get_val array not supported", "[get_val][esp_matter_managed][invalid][array]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(test_endpoint_id, Descriptor::Id, Descriptor::Attributes::ServerList::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, err);
|
||||
|
||||
err = attribute::get_val(test_endpoint_id, Descriptor::Id, Descriptor::Attributes::DeviceTypeList::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, err);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// Primitive Types - ESP Matter Managed
|
||||
|
||||
TEST_CASE("get_val bool - OnOff", "[get_val][esp_matter_managed][bool]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
attribute_t *attr = attribute::get(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
esp_matter_attr_val_t setable_val = esp_matter_bool(true);
|
||||
esp_err_t err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t true_val;
|
||||
err = attribute::get_val(attr, &true_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, true_val.type);
|
||||
TEST_ASSERT_EQUAL(true, true_val.val.b);
|
||||
|
||||
setable_val = esp_matter_bool(false);
|
||||
err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t false_val;
|
||||
err = attribute::get_val(attr, &false_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, false_val.type);
|
||||
TEST_ASSERT_EQUAL(false, false_val.val.b);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val uint8 - ColorMode", "[get_val][esp_matter_managed][uint8]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
attribute_t *attr = attribute::get(test_endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorMode::Id);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
esp_matter_attr_val_t setable_val = esp_matter_enum8(0);
|
||||
esp_err_t err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t retrieved_val;
|
||||
err = attribute::get_val(attr, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM8, retrieved_val.type);
|
||||
TEST_ASSERT_EQUAL(0, retrieved_val.val.u8);
|
||||
|
||||
setable_val = esp_matter_enum8(1);
|
||||
err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
err = attribute::get_val(attr, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM8, retrieved_val.type);
|
||||
TEST_ASSERT_EQUAL(1, retrieved_val.val.u8);
|
||||
|
||||
setable_val = esp_matter_enum8(2);
|
||||
err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
err = attribute::get_val(attr, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM8, retrieved_val.type);
|
||||
TEST_ASSERT_EQUAL(2, retrieved_val.val.u8);
|
||||
|
||||
// Invalid enum value (ColorMode valid range is 0-2)
|
||||
setable_val = esp_matter_enum8(3);
|
||||
err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val uint16 - ColorTemperatureMireds", "[get_val][esp_matter_managed][uint16]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(test_endpoint_id, ColorControl::Id, ColorControl::Attributes::ColorTemperatureMireds::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16, val.type);
|
||||
TEST_ASSERT_EQUAL(test_color_temp, val.val.u16);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val uint32 - Feature map", "[get_val][esp_matter_managed][uint32]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(test_endpoint_id, OnOff::Id, Globals::Attributes::FeatureMap::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP32, val.type);
|
||||
TEST_ASSERT_EQUAL(1, val.val.u32);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// Nullable Types - ESP Matter Managed
|
||||
|
||||
TEST_CASE("get_val nullable uint8 - CurrentLevel", "[get_val][esp_matter_managed][nullable][uint8]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
attribute_t *attr = attribute::get(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
esp_matter_attr_val_t setable_val = esp_matter_nullable_uint8(test_current_level);
|
||||
esp_err_t err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
esp_matter_attr_val_t retrieved_val;
|
||||
err = attribute::get_val(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT8, retrieved_val.type);
|
||||
nullable<uint8_t> data(retrieved_val.val.u8);
|
||||
TEST_ASSERT_FALSE(data.is_null());
|
||||
TEST_ASSERT_EQUAL(test_current_level, data.value());
|
||||
setable_val = esp_matter_nullable_uint8(nullable<uint8_t>());
|
||||
err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
err = attribute::get_val(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT8, retrieved_val.type);
|
||||
nullable<uint8_t> null_data(retrieved_val.val.u8);
|
||||
TEST_ASSERT_TRUE(null_data.is_null());
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val nullable uint16 - StartUpColorTemperatureMireds", "[get_val][esp_matter_managed][nullable][uint16]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
attribute_t *attr = attribute::get(test_endpoint_id, ColorControl::Id, ColorControl::Attributes::StartUpColorTemperatureMireds::Id);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
// this is nvs type value, so it may screw up our tests if ran without clearing the nvs
|
||||
// hence, always set and verify the value
|
||||
|
||||
esp_matter_attr_val_t setable_val = esp_matter_nullable_uint16(test_color_temp);
|
||||
esp_err_t err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_TRUE(err == ESP_OK || err == ESP_ERR_NOT_FINISHED);
|
||||
|
||||
esp_matter_attr_val_t retrieved_val;
|
||||
err = attribute::get_val(attr, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT16, retrieved_val.type);
|
||||
nullable<uint16_t> data(retrieved_val.val.u16);
|
||||
TEST_ASSERT_FALSE(data.is_null());
|
||||
TEST_ASSERT_EQUAL(test_color_temp, data.value());
|
||||
|
||||
setable_val = esp_matter_nullable_uint16(nullable<uint16_t>());
|
||||
err = attribute::set_val(attr, &setable_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
err = attribute::get_val(attr, &retrieved_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT16, retrieved_val.type);
|
||||
nullable<uint16_t> data2(retrieved_val.val.u16);
|
||||
TEST_ASSERT_TRUE(data2.is_null());
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
// get_val() -> Internally Managed Attributes (by ConnectedHomeIP)
|
||||
// ====================================================================================
|
||||
|
||||
// Arrays - not supported for get_val
|
||||
TEST_CASE("get_val array not supported", "[get_val][internal_managed][invalid][array]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
|
||||
esp_err_t err = attribute::get_val(test_endpoint_id, Descriptor::Id, Descriptor::Attributes::ServerList::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, err);
|
||||
|
||||
err = attribute::get_val(test_endpoint_id, Descriptor::Id, Descriptor::Attributes::DeviceTypeList::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, err);
|
||||
|
||||
err = attribute::get_val(root_endpoint_id, Descriptor::Id, Descriptor::Attributes::PartsList::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, err);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// Primitive Types - Internally Managed
|
||||
|
||||
TEST_CASE("get_val uint16", "[get_val][internal_managed][uint16]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t vendor_id_val;
|
||||
esp_err_t err = attribute::get_val(root_endpoint_id, BasicInformation::Id, BasicInformation::Attributes::VendorID::Id, &vendor_id_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16, vendor_id_val.type);
|
||||
TEST_ASSERT_EQUAL(CONFIG_DEVICE_VENDOR_ID, vendor_id_val.val.u16);
|
||||
esp_matter_attr_val_t product_id_val;
|
||||
err = attribute::get_val(root_endpoint_id, BasicInformation::Id, BasicInformation::Attributes::ProductID::Id, &product_id_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16, product_id_val.type);
|
||||
TEST_ASSERT_EQUAL(CONFIG_DEVICE_PRODUCT_ID, product_id_val.val.u16);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val uint32 - SoftwareVersion", "[get_val][internal_managed][uint32]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(root_endpoint_id, BasicInformation::Id, BasicInformation::Attributes::SoftwareVersion::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT32, val.type);
|
||||
TEST_ASSERT_EQUAL(1, val.val.u32);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val uint8 - MaxNetworks", "[get_val][internal_managed][uint8]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(root_endpoint_id, NetworkCommissioning::Id, NetworkCommissioning::Attributes::MaxNetworks::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, val.type);
|
||||
TEST_ASSERT_EQUAL(1, val.val.u8);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
TEST_CASE("get_val bool - SupportsConcurrentConnection", "[get_val][internal_managed][bool]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(root_endpoint_id, GeneralCommissioning::Id, GeneralCommissioning::Attributes::SupportsConcurrentConnection::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, val.type);
|
||||
TEST_ASSERT_EQUAL(true, val.val.b);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// Strings - Internally Managed
|
||||
|
||||
TEST_CASE("get_val char_string", "[get_val][internal_managed][char_string]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t vendor_name_val;
|
||||
esp_err_t err = attribute::get_val(root_endpoint_id, BasicInformation::Id, BasicInformation::Attributes::VendorName::Id, &vendor_name_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_CHAR_STRING, vendor_name_val.type);
|
||||
TEST_ASSERT_NOT_NULL(vendor_name_val.val.a.b);
|
||||
TEST_ASSERT_GREATER_THAN(0, vendor_name_val.val.a.s);
|
||||
TEST_ASSERT_EQUAL_STRING("TEST_VENDOR", vendor_name_val.val.a.b);
|
||||
free(vendor_name_val.val.a.b);
|
||||
esp_matter_attr_val_t product_name_val;
|
||||
err = attribute::get_val(root_endpoint_id, BasicInformation::Id, BasicInformation::Attributes::ProductName::Id, &product_name_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_CHAR_STRING, product_name_val.type);
|
||||
TEST_ASSERT_NOT_NULL(product_name_val.val.a.b);
|
||||
TEST_ASSERT_GREATER_THAN(0, product_name_val.val.a.s);
|
||||
TEST_ASSERT_EQUAL_STRING("TEST_PRODUCT", product_name_val.val.a.b);
|
||||
free(product_name_val.val.a.b);
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
|
||||
// Nullable Types - Internally Managed
|
||||
|
||||
TEST_CASE("get_val nullable int32 - LastConnectErrorValue", "[get_val][internal_managed][nullable][int32]")
|
||||
{
|
||||
setup_for_get_val();
|
||||
|
||||
esp_matter_attr_val_t val;
|
||||
esp_err_t err = attribute::get_val(root_endpoint_id, NetworkCommissioning::Id, NetworkCommissioning::Attributes::LastConnectErrorValue::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_INT32, val.type);
|
||||
nullable<int32_t> nullable_val(val.val.i32);
|
||||
TEST_ASSERT_EQUAL(true, nullable_val.is_null());
|
||||
|
||||
teardown_for_get_val();
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <unity.h>
|
||||
#include <esp_matter.h>
|
||||
#include <esp_matter_core.h>
|
||||
#include <esp_matter_data_model.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
using namespace esp_matter;
|
||||
|
||||
// Test data model
|
||||
static node_t *test_node = nullptr;
|
||||
static endpoint_t *test_endpoint = nullptr;
|
||||
static cluster_t *test_cluster = nullptr;
|
||||
static uint16_t test_endpoint_id = 1;
|
||||
static uint32_t test_cluster_id = 0xFFF1; // Custom test cluster
|
||||
|
||||
static bool node_created = false;
|
||||
|
||||
void setup_for_get_val_type()
|
||||
{
|
||||
if (!node_created) {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
node::config_t node_config;
|
||||
test_node = node::create(&node_config, nullptr, nullptr);
|
||||
TEST_ASSERT_NOT_NULL(test_node);
|
||||
|
||||
test_endpoint = endpoint::create(test_node, ENDPOINT_FLAG_NONE, nullptr);
|
||||
TEST_ASSERT_NOT_NULL(test_endpoint);
|
||||
test_endpoint_id = endpoint::get_id(test_endpoint);
|
||||
|
||||
node_created = true;
|
||||
}
|
||||
|
||||
// Fresh cluster per test case
|
||||
test_cluster = cluster::create(test_endpoint, test_cluster_id, CLUSTER_FLAG_SERVER);
|
||||
TEST_ASSERT_NOT_NULL(test_cluster);
|
||||
}
|
||||
|
||||
void teardown_for_get_val_type()
|
||||
{
|
||||
// Only destroy the cluster, not the endpoint — endpoint is reused across test cases
|
||||
// since node/endpoint creation involves starting the Matter stack which cannot be cleanly restarted.
|
||||
if (test_cluster) {
|
||||
cluster::destroy(test_cluster);
|
||||
test_cluster = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("invalid inputs to get_val_type", "[get_val_type][invalid]")
|
||||
{
|
||||
setup_for_get_val_type();
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type((attribute_t *)nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INVALID, type);
|
||||
|
||||
type = attribute::get_val_type(0xFFFF, 0x0006, 0x0000);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INVALID, type);
|
||||
|
||||
type = attribute::get_val_type(1, 0xFFFFFFFF, 0x0000);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INVALID, type);
|
||||
|
||||
type = attribute::get_val_type(1, 0x0006, 0xFFFFFFFF);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INVALID, type);
|
||||
|
||||
teardown_for_get_val_type();
|
||||
}
|
||||
|
||||
// Test all primitive types from attribute_utils.h
|
||||
TEST_CASE("get_val_type for all primitive types", "[get_val_type][all_types]")
|
||||
{
|
||||
setup_for_get_val_type();
|
||||
|
||||
uint32_t attr_id = 1; // Start attribute IDs from 1
|
||||
|
||||
// Test 1: Boolean
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_bool(true);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, type);
|
||||
|
||||
// Test with IDs
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, type);
|
||||
}
|
||||
|
||||
// Test 2: Integer (32-bit signed)
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_int(42);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INTEGER, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INTEGER, type);
|
||||
}
|
||||
|
||||
// Test 3: Float
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_float(3.14f);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_FLOAT, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_FLOAT, type);
|
||||
}
|
||||
|
||||
// Test 4: INT8
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_int8(-128);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT8, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT8, type);
|
||||
}
|
||||
|
||||
// Test 5: UINT8
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_uint8(255);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, type);
|
||||
}
|
||||
|
||||
// Test 6: INT16
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_int16(-32768);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT16, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT16, type);
|
||||
}
|
||||
|
||||
// Test 7: UINT16
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_uint16(65535);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16, type);
|
||||
}
|
||||
|
||||
// Test 8: INT32
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_int32(-2147483648);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT32, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT32, type);
|
||||
}
|
||||
|
||||
// Test 9: UINT32
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_uint32(0xFFFFFFFF);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT32, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT32, type);
|
||||
}
|
||||
|
||||
// Test 10: INT64
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_int64(-9223372036854775807LL);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT64, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT64, type);
|
||||
}
|
||||
|
||||
// Test 11: UINT64
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_uint64(0xFFFFFFFFFFFFFFFFULL);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT64, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT64, type);
|
||||
}
|
||||
|
||||
// Test 12: ENUM8
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_enum8(5);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM8, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM8, type);
|
||||
}
|
||||
|
||||
// Test 13: ENUM16
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_enum16(1000);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM16, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ENUM16, type);
|
||||
}
|
||||
|
||||
// Test 14: BITMAP8
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_bitmap8(0xAB);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP8, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP8, type);
|
||||
}
|
||||
|
||||
// Test 15: BITMAP16
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_bitmap16(0xABCD);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP16, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP16, type);
|
||||
}
|
||||
|
||||
// Test 16: BITMAP32
|
||||
{
|
||||
esp_matter_attr_val_t val = esp_matter_bitmap32(0xABCD1234);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP32, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BITMAP32, type);
|
||||
}
|
||||
|
||||
// Test 17: CHAR_STRING
|
||||
{
|
||||
char test_str[] = "TestString";
|
||||
esp_matter_attr_val_t val = esp_matter_char_str(test_str, strlen(test_str));
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_CHAR_STRING, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_CHAR_STRING, type);
|
||||
}
|
||||
|
||||
// Test 18: OCTET_STRING
|
||||
{
|
||||
uint8_t test_octets[] = {0x01, 0x02, 0x03, 0x04};
|
||||
esp_matter_attr_val_t val = esp_matter_octet_str(test_octets, sizeof(test_octets));
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_OCTET_STRING, type);
|
||||
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id - 1);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_OCTET_STRING, type);
|
||||
}
|
||||
|
||||
// Test 19: ARRAY
|
||||
{
|
||||
// apparently our internal impl wants this to be malloced,
|
||||
// otherwise at the time of destruction, we will try to free a stack allocated buffer
|
||||
uint8_t *test_array = (uint8_t *)malloc(3 * sizeof(uint8_t));
|
||||
TEST_ASSERT_NOT_NULL(test_array);
|
||||
test_array[0] = 1; test_array[1] = 2; test_array[2] = 3;
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_array(test_array, 3 * sizeof(uint8_t), 3);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id++, ATTRIBUTE_FLAG_NONE, val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_ARRAY, type);
|
||||
|
||||
// no need to free test_array here, it will be freed when the attribute is destroyed
|
||||
}
|
||||
|
||||
teardown_for_get_val_type();
|
||||
}
|
||||
|
||||
// Test that type persists after value updates
|
||||
TEST_CASE("get_val_type persists after attribute update", "[get_val_type][persistence]")
|
||||
{
|
||||
setup_for_get_val_type();
|
||||
|
||||
uint32_t attr_id = 100;
|
||||
// Create a UINT8 attribute
|
||||
esp_matter_attr_val_t initial_val = esp_matter_uint8(10);
|
||||
attribute_t *attr = attribute::create(test_cluster, attr_id, ATTRIBUTE_FLAG_NONE, initial_val);
|
||||
TEST_ASSERT_NOT_NULL(attr);
|
||||
|
||||
// Verify initial type
|
||||
esp_matter_val_type_t type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, type);
|
||||
|
||||
// Update value
|
||||
esp_matter_attr_val_t new_val = esp_matter_uint8(200);
|
||||
esp_err_t err = attribute::set_val(attr, &new_val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
// Type should remain the same
|
||||
type = attribute::get_val_type(attr);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, type);
|
||||
|
||||
// Verify with IDs as well
|
||||
type = attribute::get_val_type(test_endpoint_id, test_cluster_id, attr_id);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, type);
|
||||
|
||||
teardown_for_get_val_type();
|
||||
}
|
||||
|
||||
// Test multiple attributes in same cluster
|
||||
TEST_CASE("get_val_type for multiple attributes in same cluster", "[get_val_type][multiple]")
|
||||
{
|
||||
setup_for_get_val_type();
|
||||
|
||||
// Create attributes with different types
|
||||
esp_matter_attr_val_t bool_val = esp_matter_bool(false);
|
||||
attribute_t *bool_attr = attribute::create(test_cluster, 1, ATTRIBUTE_FLAG_NONE, bool_val);
|
||||
TEST_ASSERT_NOT_NULL(bool_attr);
|
||||
|
||||
esp_matter_attr_val_t uint8_val = esp_matter_uint8(42);
|
||||
attribute_t *uint8_attr = attribute::create(test_cluster, 2, ATTRIBUTE_FLAG_NONE, uint8_val);
|
||||
TEST_ASSERT_NOT_NULL(uint8_attr);
|
||||
|
||||
esp_matter_attr_val_t uint16_val = esp_matter_uint16(1234);
|
||||
attribute_t *uint16_attr = attribute::create(test_cluster, 3, ATTRIBUTE_FLAG_NONE, uint16_val);
|
||||
TEST_ASSERT_NOT_NULL(uint16_attr);
|
||||
|
||||
esp_matter_attr_val_t int32_val = esp_matter_int32(-9999);
|
||||
attribute_t *int32_attr = attribute::create(test_cluster, 4, ATTRIBUTE_FLAG_NONE, int32_val);
|
||||
TEST_ASSERT_NOT_NULL(int32_attr);
|
||||
|
||||
// Verify all types via attribute handle
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, attribute::get_val_type(bool_attr));
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8, attribute::get_val_type(uint8_attr));
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16, attribute::get_val_type(uint16_attr));
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT32, attribute::get_val_type(int32_attr));
|
||||
|
||||
// Verify all types via IDs
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN,
|
||||
attribute::get_val_type(test_endpoint_id, test_cluster_id, 1));
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT8,
|
||||
attribute::get_val_type(test_endpoint_id, test_cluster_id, 2));
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_UINT16,
|
||||
attribute::get_val_type(test_endpoint_id, test_cluster_id, 3));
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_INT32,
|
||||
attribute::get_val_type(test_endpoint_id, test_cluster_id, 4));
|
||||
|
||||
teardown_for_get_val_type();
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <unity.h>
|
||||
#include <esp_matter.h>
|
||||
#include <esp_matter_core.h>
|
||||
#include <nvs_flash.h>
|
||||
|
||||
using namespace esp_matter;
|
||||
using namespace chip::app::Clusters;
|
||||
|
||||
static node_t *test_node = nullptr;
|
||||
static endpoint_t *test_endpoint = nullptr;
|
||||
static uint16_t test_endpoint_id = 0;
|
||||
|
||||
struct callback_record_t {
|
||||
bool called;
|
||||
attribute::callback_type_t type;
|
||||
uint16_t endpoint_id;
|
||||
uint32_t cluster_id;
|
||||
uint32_t attribute_id;
|
||||
esp_matter_attr_val_t val;
|
||||
};
|
||||
|
||||
static callback_record_t cb_pre_update;
|
||||
static callback_record_t cb_post_update;
|
||||
|
||||
static void reset_callback_records()
|
||||
{
|
||||
memset(&cb_pre_update, 0, sizeof(cb_pre_update));
|
||||
memset(&cb_post_update, 0, sizeof(cb_post_update));
|
||||
}
|
||||
|
||||
static esp_err_t test_attribute_callback(attribute::callback_type_t type, uint16_t endpoint_id,
|
||||
uint32_t cluster_id, uint32_t attribute_id,
|
||||
esp_matter_attr_val_t *val, void *priv_data)
|
||||
{
|
||||
callback_record_t *record = nullptr;
|
||||
if (type == attribute::PRE_UPDATE) {
|
||||
record = &cb_pre_update;
|
||||
} else if (type == attribute::POST_UPDATE) {
|
||||
record = &cb_post_update;
|
||||
} else {
|
||||
return ESP_OK;
|
||||
}
|
||||
record->called = true;
|
||||
record->type = type;
|
||||
record->endpoint_id = endpoint_id;
|
||||
record->cluster_id = cluster_id;
|
||||
record->attribute_id = attribute_id;
|
||||
record->val = *val;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void setup_for_update_report()
|
||||
{
|
||||
static bool setup_done = false;
|
||||
if (setup_done) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_flash_init();
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
node::config_t node_config;
|
||||
test_node = node::create(&node_config, test_attribute_callback, nullptr);
|
||||
TEST_ASSERT_NOT_NULL(test_node);
|
||||
|
||||
endpoint::extended_color_light::config_t light_config;
|
||||
light_config.on_off.on_off = false;
|
||||
light_config.level_control.current_level = nullable<uint8_t>(100);
|
||||
test_endpoint = endpoint::extended_color_light::create(test_node, &light_config, ENDPOINT_FLAG_NONE, nullptr);
|
||||
TEST_ASSERT_NOT_NULL(test_endpoint);
|
||||
|
||||
test_endpoint_id = endpoint::get_id(test_endpoint);
|
||||
|
||||
err = esp_matter::start(nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
setup_done = true;
|
||||
}
|
||||
|
||||
void teardown_for_update_report()
|
||||
{
|
||||
reset_callback_records();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// attribute::report() tests
|
||||
// ============================================================
|
||||
|
||||
TEST_CASE("report returns ESP_ERR_INVALID_ARG for null val", "[report][invalid]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_err_t err = attribute::report(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, nullptr);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("report returns ESP_ERR_INVALID_ARG for invalid endpoint", "[report][invalid]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_bool(true);
|
||||
esp_err_t err = attribute::report(chip::kInvalidEndpointId, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("report returns ESP_ERR_INVALID_ARG for invalid cluster", "[report][invalid]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_bool(true);
|
||||
esp_err_t err = attribute::report(test_endpoint_id, chip::kInvalidClusterId, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("report returns ESP_ERR_INVALID_ARG for invalid attribute", "[report][invalid]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_bool(true);
|
||||
esp_err_t err = attribute::report(test_endpoint_id, OnOff::Id, chip::kInvalidAttributeId, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("report bool updates stored value", "[report][bool]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_bool(true);
|
||||
esp_err_t err = attribute::report(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t retrieved;
|
||||
err = attribute::get_val(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &retrieved);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, retrieved.type);
|
||||
TEST_ASSERT_EQUAL(true, retrieved.val.b);
|
||||
|
||||
val = esp_matter_bool(false);
|
||||
err = attribute::report(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
err = attribute::get_val(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &retrieved);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(false, retrieved.val.b);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("report nullable uint8 updates stored value", "[report][nullable][uint8]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_nullable_uint8(150);
|
||||
esp_err_t err = attribute::report(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t retrieved;
|
||||
err = attribute::get_val(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &retrieved);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT8, retrieved.type);
|
||||
nullable<uint8_t> data(retrieved.val.u8);
|
||||
TEST_ASSERT_FALSE(data.is_null());
|
||||
TEST_ASSERT_EQUAL(150, data.value());
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("report same value returns ESP_OK", "[report][no_change]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_bool(false);
|
||||
esp_err_t err = attribute::report(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
err = attribute::report(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// report() must NOT invoke attribute callbacks
|
||||
// ============================================================
|
||||
|
||||
TEST_CASE("report does not call attribute callbacks", "[report][callback]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
reset_callback_records();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_nullable_uint8(42);
|
||||
esp_err_t err = attribute::report(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
TEST_ASSERT_FALSE(cb_pre_update.called);
|
||||
TEST_ASSERT_FALSE(cb_post_update.called);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// update() MUST invoke attribute callbacks with correct values
|
||||
// ============================================================
|
||||
|
||||
TEST_CASE("update calls PRE_UPDATE and POST_UPDATE callbacks", "[update][callback]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
reset_callback_records();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_bool(true);
|
||||
esp_err_t err = attribute::update(test_endpoint_id, OnOff::Id, OnOff::Attributes::OnOff::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
TEST_ASSERT_TRUE(cb_pre_update.called);
|
||||
TEST_ASSERT_EQUAL(attribute::PRE_UPDATE, cb_pre_update.type);
|
||||
TEST_ASSERT_EQUAL(test_endpoint_id, cb_pre_update.endpoint_id);
|
||||
TEST_ASSERT_EQUAL(OnOff::Id, cb_pre_update.cluster_id);
|
||||
TEST_ASSERT_EQUAL(OnOff::Attributes::OnOff::Id, cb_pre_update.attribute_id);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, cb_pre_update.val.type);
|
||||
TEST_ASSERT_EQUAL(true, cb_pre_update.val.val.b);
|
||||
|
||||
TEST_ASSERT_TRUE(cb_post_update.called);
|
||||
TEST_ASSERT_EQUAL(attribute::POST_UPDATE, cb_post_update.type);
|
||||
TEST_ASSERT_EQUAL(test_endpoint_id, cb_post_update.endpoint_id);
|
||||
TEST_ASSERT_EQUAL(OnOff::Id, cb_post_update.cluster_id);
|
||||
TEST_ASSERT_EQUAL(OnOff::Attributes::OnOff::Id, cb_post_update.attribute_id);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_BOOLEAN, cb_post_update.val.type);
|
||||
TEST_ASSERT_EQUAL(true, cb_post_update.val.val.b);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
TEST_CASE("update calls callbacks with correct value for nullable uint8", "[update][callback][nullable][uint8]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
reset_callback_records();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_nullable_uint8(77);
|
||||
esp_err_t err = attribute::update(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
TEST_ASSERT_TRUE(cb_pre_update.called);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT8, cb_pre_update.val.type);
|
||||
TEST_ASSERT_EQUAL(77, cb_pre_update.val.val.u8);
|
||||
|
||||
TEST_ASSERT_TRUE(cb_post_update.called);
|
||||
TEST_ASSERT_EQUAL(ESP_MATTER_VAL_TYPE_NULLABLE_UINT8, cb_post_update.val.type);
|
||||
TEST_ASSERT_EQUAL(77, cb_post_update.val.val.u8);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// update() and report() behavioral parity (stored value)
|
||||
// ============================================================
|
||||
|
||||
TEST_CASE("update and report both store the value correctly", "[update][report_parity]")
|
||||
{
|
||||
setup_for_update_report();
|
||||
|
||||
esp_matter_attr_val_t val = esp_matter_nullable_uint8(55);
|
||||
esp_err_t err = attribute::update(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t after_update;
|
||||
err = attribute::get_val(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &after_update);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(55, after_update.val.u8);
|
||||
|
||||
val = esp_matter_nullable_uint8(88);
|
||||
err = attribute::report(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &val);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
|
||||
esp_matter_attr_val_t after_report;
|
||||
err = attribute::get_val(test_endpoint_id, LevelControl::Id, LevelControl::Attributes::CurrentLevel::Id, &after_report);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_EQUAL(88, after_report.val.u8);
|
||||
|
||||
TEST_ASSERT_EQUAL(after_update.type, after_report.type);
|
||||
|
||||
teardown_for_update_report();
|
||||
}
|
||||
@@ -134,8 +134,15 @@ examples/bridge_apps/esp_rainmaker_bridge:
|
||||
- if: IDF_TARGET in ["esp32s3"]
|
||||
temporary: true
|
||||
reason: the other targets are not tested yet
|
||||
|
||||
examples/camera:
|
||||
enable:
|
||||
- if: IDF_TARGET in [""]
|
||||
temporary: true
|
||||
reason: Another CI has been added
|
||||
|
||||
examples/unit_test_app:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32c3"]
|
||||
temporary: true
|
||||
reason: the other targets are not tested yet
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(PROJECT_VER "1.0")
|
||||
set(PROJECT_VER_NUMBER 1)
|
||||
|
||||
set(ESP_MATTER_PATH $ENV{ESP_MATTER_PATH})
|
||||
set(MATTER_SDK_PATH ${ESP_MATTER_PATH}/connectedhomeip/connectedhomeip)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../../components"
|
||||
"${MATTER_SDK_PATH}/config/esp32/components")
|
||||
|
||||
# Set the components to include the tests for.
|
||||
set(TEST_COMPONENTS "esp_matter" CACHE STRING "List of components to test")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(unit_test_app)
|
||||
|
||||
# TODO: Remove -Wno-error=unused-result once submodules are updated to not treat unused return values as errors.
|
||||
idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++17;-Os;-DCHIP_HAVE_CONFIG_H;-Wno-overloaded-virtual;-Wno-error=unused-result" APPEND)
|
||||
idf_build_set_property(C_COMPILE_OPTIONS "-Os" APPEND)
|
||||
# For RISCV chips, project_include.cmake sets -Wno-format, but does not clear various
|
||||
# flags that depend on -Wformat
|
||||
idf_build_set_property(COMPILE_OPTIONS "-Wno-format-nonliteral;-Wno-format-security" APPEND)
|
||||
@@ -0,0 +1,109 @@
|
||||
# ESP Matter Unit Test App
|
||||
|
||||
This application runs unit tests for the ESP Matter component using the Unity test framework.
|
||||
|
||||
## Unity Test Framework
|
||||
|
||||
This test app uses the Unity Test Framework suggested by ESP-IDF.
|
||||
|
||||
Further reads:
|
||||
- [Unit Testing with Unity](https://docs.espressif.com/projects/vscode-esp-idf-extension/en/latest/additionalfeatures/unit-testing.html)
|
||||
- [Unit Testing in ESP32](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/unit-tests.html)
|
||||
|
||||
## Running the Tests
|
||||
|
||||
1. Build the application:
|
||||
```bash
|
||||
cd examples/unit_test_app
|
||||
idf.py build
|
||||
```
|
||||
|
||||
2. Flash to device:
|
||||
```bash
|
||||
idf.py -p <PORT> flash monitor
|
||||
```
|
||||
|
||||
3. Run tests:
|
||||
Once flashed, the test menu will appear in the serial monitor. You can:
|
||||
- Press `Enter` to see the list of available tests
|
||||
- Enter a test number to run a specific test
|
||||
- Enter `*` to run all tests
|
||||
|
||||
## Running Tests with QEMU (no hardware needed)
|
||||
|
||||
You can run the unit tests locally under QEMU emulation without physical hardware. This is the same method used in CI.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Install QEMU for RISC-V (esp32c3):
|
||||
```bash
|
||||
python3 -m pip install pytest-embedded-qemu
|
||||
```
|
||||
|
||||
- Ensure the QEMU binary (`qemu-system-riscv32`) is available. Install it via ESP-IDF tools:
|
||||
```bash
|
||||
$IDF_PATH/tools/idf_tools.py install qemu-riscv32
|
||||
source $IDF_PATH/export.sh # re-source to pick up the new tool in PATH
|
||||
```
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
cd examples/unit_test_app
|
||||
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.qemu" set-target esp32c3 build
|
||||
|
||||
# Run all QEMU test groups (each gets a fresh QEMU reboot)
|
||||
pytest pytest_unit_test_app.py \
|
||||
--target esp32c3 \
|
||||
-m qemu \
|
||||
--embedded-services idf,qemu \
|
||||
--qemu-extra-args="-global driver=timer.esp32c3.timg,property=wdt_disable,value=true"
|
||||
|
||||
# Run a single test group
|
||||
pytest pytest_unit_test_app.py \
|
||||
--target esp32c3 \
|
||||
-m qemu \
|
||||
--embedded-services idf,qemu \
|
||||
--qemu-extra-args="-global driver=timer.esp32c3.timg,property=wdt_disable,value=true" \
|
||||
-k test_get_val
|
||||
```
|
||||
|
||||
### Why multiple test functions?
|
||||
|
||||
Each test file has its own `setup_*()` function that calls `esp_matter::start()`, and there is no teardown/stop.
|
||||
Since only one setup can succeed per boot, tests are grouped so each group runs after a fresh QEMU reboot.
|
||||
Each pytest function (eg: `test_get_val`, `test_get_val_type`, `test_update_report`) gets its own QEMU instance.
|
||||
|
||||
## Extending the Tests
|
||||
|
||||
### Adding tests to existing component
|
||||
|
||||
1. Create a new `.cpp` file in `components/<component_name>/test/`
|
||||
2. Add the filename to the source list in `components/<component_name>/test/CMakeLists.txt`:
|
||||
```cmake
|
||||
list(APPEND srcs_list "your_new_test_file.cpp")
|
||||
```
|
||||
3. Write the test cases in the new test file.
|
||||
|
||||
### Adding new component tests
|
||||
|
||||
Please refer to components/esp_matter/test directory for comprehensive structure and example.
|
||||
|
||||
- After adding the new component tests, you need to add the component to the TEST_COMPONENTS list in CMakeLists.txt
|
||||
- Append the component name to the TEST_COMPONENTS list. For example, if you add a new component called "new_component",
|
||||
you need to add it to the TEST_COMPONENTS list in CMakeLists.txt:
|
||||
|
||||
```cmake
|
||||
set(TEST_COMPONENTS "esp_matter new_component" CACHE STRING "List of components to test")
|
||||
```
|
||||
|
||||
### For running them in the CI,
|
||||
- Add the test group to the `pytest_unit_test_app.py` file with the appropriate marker and test function name.
|
||||
|
||||
```python
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.esp32c3
|
||||
def test_my_unit_tests(dut: QemuDut) -> None:
|
||||
run_group(dut, 'my_test_group')
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "app_main.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES unity esp_matter)
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "unity.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "UT";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "esp-matter unit test app");
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: Firmware partition offset needs to be 64K aligned, initial 36K (9 sectors) are reserved for bootloader and partition table
|
||||
esp_secure_cert, 0x3F, ,0xd000, 0x2000, encrypted
|
||||
nvs, data, nvs, 0x10000, 0xC000,
|
||||
nvs_keys, data, nvs_keys,, 0x1000, encrypted
|
||||
otadata, data, ota, , 0x2000
|
||||
phy_init, data, phy, , 0x1000,
|
||||
ota_0, app, ota_0, 0x20000, 0x1E0000,
|
||||
ota_1, app, ota_1, 0x200000, 0x1E0000,
|
||||
fctry, data, nvs, 0x3E0000, 0x6000
|
||||
|
@@ -0,0 +1,44 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_qemu.dut import QemuDut
|
||||
|
||||
|
||||
def run_group(dut: QemuDut, group: str, timeout: int = 120) -> None:
|
||||
"""Run all Unity cases matching a group tag, then verify no failures.
|
||||
|
||||
pytest-embedded records Unity results without raising on failure,
|
||||
so we check dut.testsuite afterwards to surface failures to pytest.
|
||||
"""
|
||||
cases = [c for c in dut.test_menu if group in c.groups]
|
||||
assert cases, f'No cases for group "{group}" (parsed {len(dut.test_menu)} total)'
|
||||
|
||||
dut.run_all_single_board_cases(group=group, timeout=timeout)
|
||||
|
||||
failed = dut.testsuite.failed_cases
|
||||
if failed:
|
||||
names = [tc.name for tc in failed]
|
||||
pytest.fail(f'{len(failed)} failed in [{group}]: {", ".join(names)}')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.esp32c3
|
||||
def test_get_val(dut: QemuDut) -> None:
|
||||
run_group(dut, 'get_val')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.esp32c3
|
||||
def test_get_val_type(dut: QemuDut) -> None:
|
||||
run_group(dut, 'get_val_type')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@pytest.mark.esp32c3
|
||||
def test_update_report(dut: QemuDut) -> None:
|
||||
run_group(dut, 'report')
|
||||
run_group(dut, 'update')
|
||||
@@ -0,0 +1,57 @@
|
||||
# Unity Framework Configuration
|
||||
CONFIG_UNITY_ENABLE_FLOAT=y
|
||||
CONFIG_UNITY_ENABLE_DOUBLE=y
|
||||
CONFIG_UNITY_ENABLE_64BIT=y
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
|
||||
#enable BT
|
||||
CONFIG_BT_ENABLED=n
|
||||
#enable lwip ipv6 autoconfig
|
||||
CONFIG_LWIP_IPV6_AUTOCONFIG=y
|
||||
|
||||
# Use a custom partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0xC000
|
||||
|
||||
# Enable chip shell
|
||||
CONFIG_ENABLE_CHIP_SHELL=y
|
||||
|
||||
# Disable WiFi — unit tests don't need networking and WiFi PHY calibration hangs in QEMU
|
||||
CONFIG_ENABLE_WIFI_STATION=n
|
||||
CONFIG_ENABLE_WIFI_AP=n
|
||||
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n
|
||||
|
||||
# Use QEMU virtual Ethernet instead of WiFi
|
||||
CONFIG_ETH_USE_OPENETH=y
|
||||
CONFIG_ENABLE_ETHERNET_TELEMETRY=y
|
||||
|
||||
#enable lwIP route hooks
|
||||
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
|
||||
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
|
||||
|
||||
# This example only use 2 dynamic endpoints
|
||||
CONFIG_ESP_MATTER_MAX_DYNAMIC_ENDPOINT_COUNT=16
|
||||
|
||||
# Enable HKDF in mbedtls
|
||||
CONFIG_MBEDTLS_HKDF_C=y
|
||||
|
||||
# Increase LwIP IPv6 address number to 6 (MAX_FABRIC + 1)
|
||||
# unique local addresses for fabrics(MAX_FABRIC), a link local address(1)
|
||||
CONFIG_LWIP_IPV6_NUM_ADDRESSES=6
|
||||
|
||||
# borrowed from unit-test-app
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
|
||||
CONFIG_SPI_FLASH_ENABLE_COUNTERS=y
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
|
||||
CONFIG_COMPILER_STACK_CHECK=y
|
||||
CONFIG_ADC_DISABLE_DAC=n
|
||||
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
|
||||
CONFIG_SPI_MASTER_IN_IRAM=y
|
||||
CONFIG_EFUSE_VIRTUAL=y
|
||||
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Disable WiFi — unit tests don't need networking and WiFi PHY calibration hangs in QEMU
|
||||
CONFIG_ENABLE_WIFI_STATION=n
|
||||
# Use QEMU virtual Ethernet instead of WiFi
|
||||
CONFIG_ETH_USE_OPENETH=y
|
||||
CONFIG_ENABLE_ETHERNET_TELEMETRY=y
|
||||
@@ -25,6 +25,8 @@ markers =
|
||||
esp32s3: support esp32s3 target
|
||||
# env markers
|
||||
esp_matter_dut: esp matter runner which have single dut
|
||||
host_test: test runs on host machine (not on target hardware)
|
||||
qemu: test runs under QEMU emulation
|
||||
|
||||
# log related
|
||||
log_cli = True
|
||||
|
||||
Reference in New Issue
Block a user