/* * Copyright (c) 2020, Armink, * * SPDX-License-Identifier: Apache-2.0 */ /** * @file * @brief KVDB testcases. * * This testcases is be used in RT-Thread Utest framework. * If you want run it, please add it to RT-Thread project. */ #include "utest.h" #include #include #include #include #define RT_USING_UTEST #if RT_VER_NUM <= 0x40003 #include #else #include #include #endif #define TEST_TS_PART_NAME "fdb_kvdb1" #define TEST_KV_BLOB_NAME "kv_blob_test" #define TEST_KV_NAME "kv_test" #define TEST_KV_VALUE_LEN 1200 /* only save 3 KVs in a 4096 sector */ #define TEST_KV_MAX_NUM 8 #define TEST_KVDB_SECTOR_SIZE 4096 #define TEST_KVDB_SECTOR_NUM 4 #define FDB_ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) #if defined(RT_USING_UTEST) && defined(FDB_USING_KVDB) struct test_kv{ char name[32]; uint8_t value[TEST_KV_VALUE_LEN]; uint32_t addr; uint32_t saved_data_size; bool is_changed; }; static struct fdb_kvdb test_kvdb; static void test_fdb_kvdb_deinit(void); static rt_err_t dir_delete(const char* path) { DIR* dir = NULL; struct dirent* dirent = NULL; char* full_path; rt_err_t res = RT_EOK; if (path == RT_NULL || path[0] == '\0') return -ENOENT; full_path = (char*)rt_malloc(DFS_PATH_MAX); if (full_path == RT_NULL) return -ENOMEM; dir = opendir(path); if (dir == RT_NULL) { rt_free(full_path); return -ENOENT; } while (1) { dirent = readdir(dir); if (dirent == RT_NULL) break; if (rt_strcmp(".", dirent->d_name) != 0 && rt_strcmp("..", dirent->d_name) != 0) { rt_snprintf(full_path, DFS_PATH_MAX, "%s/%s", path, dirent->d_name); if (dirent->d_type == DT_REG) { if (unlink(full_path) != 0) { LOG_W("cannot remove '%s'", full_path); res = -RT_ERROR; } } else if (dirent->d_type == DT_DIR) { if (dir_delete(full_path) != RT_EOK) { res = -RT_ERROR; } } } } closedir(dir); rt_free(full_path); if (path[rt_strlen(path) - 1] != '/') { if (unlink(path) != 0) { LOG_W("cannot remove '%s'", path); res = -RT_ERROR; } } return res; } static void test_fdb_kvdb_init_by_sector_num(size_t sector_num) { if (access(TEST_TS_PART_NAME, 0) < 0) { mkdir(TEST_TS_PART_NAME, 0); } uint32_t sec_size = TEST_KVDB_SECTOR_SIZE, db_size = sec_size * sector_num; rt_bool_t file_mode = true; fdb_kvdb_control(&(test_kvdb), FDB_KVDB_CTRL_SET_SEC_SIZE, &sec_size); fdb_kvdb_control(&(test_kvdb), FDB_KVDB_CTRL_SET_FILE_MODE, &file_mode); fdb_kvdb_control(&(test_kvdb), FDB_KVDB_CTRL_SET_MAX_SIZE, &db_size); uassert_true(fdb_kvdb_init(&test_kvdb, "test_kv", TEST_TS_PART_NAME, NULL, NULL) == FDB_NO_ERR); } static void test_fdb_kvdb_init(void) { test_fdb_kvdb_init_by_sector_num(TEST_KVDB_SECTOR_NUM); } static void test_fdb_kvdb_init_by_8_sectors(void) { test_fdb_kvdb_init_by_sector_num(8); } static void test_fdb_kvdb_init_check(void) { /* check the oldest address */ uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); } static void test_fdb_kvdb_deinit(void) { uassert_true(fdb_kvdb_deinit(&test_kvdb) == FDB_NO_ERR); } static void test_fdb_create_kv_blob(void) { fdb_err_t result = FDB_NO_ERR; rt_tick_t tick = rt_tick_get(), read_tick; size_t read_len; struct fdb_kv kv_obj; struct fdb_blob blob; uint8_t value_buf[sizeof(tick)]; result = fdb_kv_set_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob, &tick, sizeof(tick))); uassert_true(result == FDB_NO_ERR); read_len = fdb_kv_get_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob, &read_tick, sizeof(read_tick))); uassert_int_equal(blob.saved.len, sizeof(read_tick)); uassert_int_equal(blob.saved.len, read_len); uassert_int_equal(tick, read_tick); uassert_true(fdb_kv_get_obj(&test_kvdb, TEST_KV_BLOB_NAME, &kv_obj) != NULL); fdb_blob_make(&blob, value_buf, sizeof(value_buf)); read_len = fdb_blob_read((fdb_db_t)&test_kvdb, fdb_kv_to_blob(&kv_obj, &blob)); uassert_int_equal(read_len, sizeof(value_buf)); uassert_buf_equal(&tick, value_buf, sizeof(value_buf)); } static void test_fdb_change_kv_blob(void) { fdb_err_t result = FDB_NO_ERR; rt_tick_t tick = rt_tick_get(), read_tick; size_t read_len; struct fdb_blob blob_obj, *blob = &blob_obj; read_len = fdb_kv_get_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob_obj, &read_tick, sizeof(read_tick))); uassert_int_equal(blob->saved.len, sizeof(read_tick)); uassert_int_equal(blob->saved.len, read_len); uassert_int_not_equal(tick, read_tick); result = fdb_kv_set_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob_obj, &tick, sizeof(tick))); uassert_true(result == FDB_NO_ERR); read_len = fdb_kv_get_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob_obj, &read_tick, sizeof(read_tick))); uassert_int_equal(blob->saved.len, sizeof(read_tick)); uassert_int_equal(blob->saved.len, read_len); uassert_int_equal(tick, read_tick); } static void test_fdb_del_kv_blob(void) { fdb_err_t result = FDB_NO_ERR; rt_tick_t tick = rt_tick_get(), read_tick; size_t read_len; struct fdb_blob blob; read_len = fdb_kv_get_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob, &read_tick, sizeof(read_tick))); uassert_int_equal(blob.saved.len, sizeof(read_tick)); uassert_int_equal(blob.saved.len, read_len); uassert_int_not_equal(tick, read_tick); result = fdb_kv_set_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob, NULL, 0)); uassert_true(result == FDB_NO_ERR); read_len = fdb_kv_get_blob(&test_kvdb, TEST_KV_BLOB_NAME, fdb_blob_make(&blob, &read_tick, sizeof(read_tick))); uassert_int_equal(blob.saved.len, 0); uassert_int_equal(read_len, 0); } static void test_fdb_create_kv(void) { fdb_err_t result = FDB_NO_ERR; rt_tick_t tick = rt_tick_get(), read_tick; char value_buf[14], *read_value; snprintf(value_buf, sizeof(value_buf), "%" PRIu32, tick); result = fdb_kv_set(&test_kvdb, TEST_KV_NAME, value_buf); uassert_true(result == FDB_NO_ERR); read_value = fdb_kv_get(&test_kvdb, TEST_KV_NAME); uassert_not_null(read_value); read_tick = atoi(read_value); uassert_int_equal(tick, read_tick); } static void test_fdb_change_kv(void) { fdb_err_t result = FDB_NO_ERR; rt_tick_t tick = rt_tick_get(), read_tick; char value_buf[14], *read_value; read_value = fdb_kv_get(&test_kvdb, TEST_KV_NAME); uassert_not_null(read_value); read_tick = atoi(read_value); uassert_int_not_equal(tick, read_tick); snprintf(value_buf, sizeof(value_buf), "%" PRIu32, tick); result = fdb_kv_set(&test_kvdb, TEST_KV_NAME, value_buf); uassert_true(result == FDB_NO_ERR); read_value = fdb_kv_get(&test_kvdb, TEST_KV_NAME); uassert_not_null(read_value); read_tick = atoi(read_value); uassert_int_equal(tick, read_tick); } /* check the oldest address is already right when kvdb reinit */ static void fdb_reboot(void) { test_fdb_kvdb_deinit(); test_fdb_kvdb_init(); } static void test_fdb_del_kv(void) { fdb_err_t result = FDB_NO_ERR; rt_tick_t tick = rt_tick_get(), read_tick; char *read_value; read_value = fdb_kv_get(&test_kvdb, TEST_KV_NAME); uassert_not_null(read_value); read_tick = atoi(read_value); uassert_int_not_equal(tick, read_tick); result = fdb_kv_del(&test_kvdb, TEST_KV_NAME); uassert_true(result == FDB_NO_ERR); read_value = fdb_kv_get(&test_kvdb, TEST_KV_NAME); uassert_null(read_value); { uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); fdb_reboot(); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); } } static int iter_all_kv(fdb_kvdb_t db, struct test_kv *kv_tbl, size_t len) { struct fdb_kv_iterator iterator; fdb_kv_t cur_kv; size_t data_size = 0; int index = 0; struct fdb_blob fdb_blob; fdb_kv_iterator_init(db, &iterator); while (fdb_kv_iterate(db, &iterator) == RT_TRUE && index < len) { /* get data len */ cur_kv = &(iterator.curr_kv); data_size = (size_t)cur_kv->value_len; rt_strncpy(kv_tbl[index].name, cur_kv->name, 32); kv_tbl[index].saved_data_size = data_size; kv_tbl[index].addr = cur_kv->addr.start; /* read data */ fdb_blob_read((fdb_db_t)db, fdb_kv_to_blob(cur_kv, fdb_blob_make(&fdb_blob, &kv_tbl[index].value, data_size))); uassert_true(kv_tbl[index].saved_data_size <= TEST_KV_VALUE_LEN); index++; } return index; } static void test_save_fdb_by_kvs(const struct test_kv *kv_tbl, size_t len) { struct fdb_blob blob_obj, * blob = &blob_obj; for (size_t i = 0; i < len; i++) { if (kv_tbl[i].is_changed) { fdb_kv_set_blob(&test_kvdb, kv_tbl[i].name, fdb_blob_make(blob, kv_tbl[i].value, TEST_KV_VALUE_LEN)); } } } static void test_check_fdb_by_kvs(const struct test_kv *kv_tbl, size_t len) { static struct test_kv saved_kv_tbl[TEST_KV_MAX_NUM] = { 0 }; iter_all_kv(&test_kvdb, saved_kv_tbl, FDB_ARRAY_SIZE(saved_kv_tbl)); for (size_t i = 0, j = 0; i < len; i++) { for (j = 0; j < FDB_ARRAY_SIZE(saved_kv_tbl); j++) { if (rt_strcmp(saved_kv_tbl[j].name, kv_tbl[i].name) == 0) break; } if (j < FDB_ARRAY_SIZE(saved_kv_tbl)) { uassert_str_equal(saved_kv_tbl[j].name, kv_tbl[i].name); uassert_str_equal(saved_kv_tbl[j].value, kv_tbl[i].value); uassert_true(RT_ALIGN_DOWN(saved_kv_tbl[j].addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * kv_tbl[i].addr); } else { /* kv not found */ uassert_true(0); } } } static void test_fdb_by_kvs(const struct test_kv *kv_tbl, size_t len) { test_save_fdb_by_kvs(kv_tbl, len); test_check_fdb_by_kvs(kv_tbl, len); } static void test_fdb_gc(void) { fdb_kv_set_default(&test_kvdb); { /* * prepare1: add 4 KVs * * +---------------------------------------------------------+ * | sector0 | sector1 | sector2 | sector3 | * | using | using | empty | empty | * +---------------------------------------------------------+ * | | | | | * | kv0 | kv3 | | | * | new | new | | | * +----------------------------+ | | * | | | | | * | kv1 | | | | * | new | | | | * +--------------+ | | | * | | | | | * | kv2 | | | | * | new | | | | * +--------------+ | | | * | | | | | * +---------------------------------------------------------+ */ static const struct test_kv kv_tbl[] = { {"kv0", "0", 0, 0, 1}, {"kv1", "1", 0, 0, 1}, {"kv2", "2", 0, 0, 1}, {"kv3", "3", 1, 0, 1}, }; test_fdb_by_kvs(kv_tbl, FDB_ARRAY_SIZE(kv_tbl)); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); fdb_reboot(); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); } { /* * prepare2: change kv0 and kv3 * * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | using | using | empty | empty | * +---------------------------------------------------------+ * | | | | | * | kv0 | kv3 | | | * | delete | delete | | | * +----------------------------+ | | * | | | | | * | kv1 | kv0 | | | * | new | new | | | * +----------------------------+ | | * | | | | | * | kv2 | kv3 | | | * | new | new | | | * +----------------------------+ | | * | | | | | * +--------------+-------------+--------------+-------------+ */ static const struct test_kv kv_tbl[] = { {"kv1", "1", 0, 0, 0}, {"kv2", "2", 0, 0, 0}, {"kv0", "00", 1, 0, 1}, {"kv3", "33", 1, 0, 1}, }; test_fdb_by_kvs(kv_tbl, FDB_ARRAY_SIZE(kv_tbl)); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); fdb_reboot(); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 0); } { /* * change kv0, kv1, kv2 and kv3, trigger GC * * step1: change kv0, kv1 and kv2 * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | using | using | using | empty | * +---------------------------------------------------------+ * | | | | | * | kv0 | kv3 | kv0 | | * | delete | delete | new | | * +-------------------------------------------+ | * | | | | | * | kv1 | kv0 | kv1 | | * | delete | delete | new | | * +-------------------------------------------+ | * | | | | | * | kv2 | kv3 | kv2 | | * | delete | new | new | | * +-------------------------------------------+ | * | | | | | * +--------------+-------------+--------------+-------------+ * * step2.1: change kv3, start GC, only GC 1 sector on FlashDB V1.2 * * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | empty | using | using | empty | * +---------------------------------------------------------+ * | | | | | * | | kv3 | kv0 | | * | | delete | new | | * | +----------------------------+ | * | | | | | * | | kv0 | kv1 | | * | | delete | new | | * | +----------------------------+ | * | | | | | * | | kv3 | kv2 | | * | | new | new | | * | +----------------------------+ | * | | | | | * +--------------+-------------+--------------+-------------+ * * step2.2: change kv3 * * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | empty | using | using | using | * +---------------------------------------------------------+ * | | | | | * | | kv3 | kv0 | kv3 | * | | delete | new | new | * | +------------------------------------------+ * | | | | | * | | kv0 | kv1 | | * | | delete | new | | * | +----------------------------+ | * | | | | | * | | kv3 | kv2 | | * | | delete | new | | * | +----------------------------+ | * | | | | | * +--------------+-------------+--------------+-------------+ * */ static const struct test_kv kv_tbl[] = { {"kv0", "000", 2, 0, 1}, {"kv1", "111", 2, 0, 1}, {"kv2", "222", 2, 0, 1}, {"kv3", "333", 3, 0, 1}, }; test_fdb_by_kvs(kv_tbl, FDB_ARRAY_SIZE(kv_tbl)); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 1); fdb_reboot(); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 1); } { /* * change kv0, kv1, kv2 and kv3, trigger GC * * change kv0 and kv1 * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | empty | using | using | using | * +---------------------------------------------------------+ * | | | | | * | | kv3 | kv0 | kv3 | * | | delete | delete | new | * | +------------------------------------------+ * | | | | | * | | kv0 | kv1 | kv0 | * | | delete | delete | new | * | +------------------------------------------+ * | | | | | * | | kv3 | kv2 | kv1 | * | | delete | new | new | * | +------------------------------------------+ * | | | | | * +--------------+-------------+--------------+-------------+ * * step2.1: change kv2, start GC, only GC 1 sector on FlashDB V1.2 * * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | empty | empty | using | using | * +---------------------------------------------------------+ * | | | | | * | | | kv0 | kv3 | * | | | delete | new | * | | +----------------------------+ * | | | | | * | | | kv1 | kv0 | * | | | delete | new | * | | +----------------------------+ * | | | | | * | | | kv2 | kv1 | * | | | new | new | * | | +----------------------------+ * | | | | | * +--------------+-------------+--------------+-------------+ * * step2.2: change kv2 * * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | uings | empty | using | using | * +---------------------------------------------------------+ * | | | | | * | kv2 | | kv0 | kv3 | * | new | | delete | new | * +--------------+ +----------------------------+ * | | | | | * | | | kv1 | kv0 | * | | | delete | new | * | | +----------------------------+ * | | | | | * | | | kv2 | kv1 | * | | | delete | new | * | | +----------------------------+ * | | | | | * +--------------+-------------+--------------+-------------+ * * step3: change kv3 * * +--------------+-------------+--------------+-------------+ * | sector0 | sector1 | sector2 | sector3 | * | using | empty | using | using | * +---------------------------------------------------------+ * | | | | | * | kv2 | | kv0 | kv3 | * | new | | delete | delete | * +--------------+ +----------------------------+ * | | | | | * | kv3 | | kv1 | kv0 | * | new | | delete | new | * +--------------+ +----------------------------+ * | | | | | * | | | kv2 | kv1 | * | | | delete | new | * | | +----------------------------+ * | | | | | * +--------------+-------------+--------------+-------------+ */ static const struct test_kv kv_tbl[] = { {"kv0", "0000", 3, 0, 1}, {"kv1", "1111", 3, 0, 1}, {"kv2", "2222", 0, 0, 1}, {"kv3", "3333", 0, 0, 1}, }; test_fdb_by_kvs(kv_tbl, FDB_ARRAY_SIZE(kv_tbl)); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 2); fdb_reboot(); uassert_true(RT_ALIGN_DOWN(test_kvdb.parent.oldest_addr, TEST_KVDB_SECTOR_SIZE) == TEST_KVDB_SECTOR_SIZE * 2); } } static void test_fdb_scale_up(void) { fdb_kv_set_default(&test_kvdb); static const struct test_kv old_kv_tbl[] = { {"kv0", "0", 0, 0, 1}, {"kv1", "1", 0, 0, 1}, {"kv2", "2", 0, 0, 1}, {"kv3", "3", 1, 0, 1}, }; /* save some data */ test_save_fdb_by_kvs(old_kv_tbl, FDB_ARRAY_SIZE(old_kv_tbl)); /* reboot, scale up from 4 sectors to 8 sectors */ test_fdb_kvdb_deinit(); test_fdb_kvdb_init_by_8_sectors(); /* check old data */ test_check_fdb_by_kvs(old_kv_tbl, FDB_ARRAY_SIZE(old_kv_tbl)); /* save some new data */ static const struct test_kv new_kv_tbl[] = { {"kv4", "4", 4, 0, 1}, {"kv5", "5", 4, 0, 1}, {"kv6", "6", 4, 0, 1}, {"kv7", "7", 5, 0, 1}, }; /* kv4: sector1, kv5: sector1, kv6: sector2, kv7: sector2 */ test_save_fdb_by_kvs(new_kv_tbl, FDB_ARRAY_SIZE(new_kv_tbl)); /* kv4: sector2, kv5: sector3, kv6: sector3, kv7: sector3 */ test_save_fdb_by_kvs(new_kv_tbl, FDB_ARRAY_SIZE(new_kv_tbl)); /* kv4: sector4, kv5: sector4, kv6: sector4, kv7: sector5 */ test_save_fdb_by_kvs(new_kv_tbl, FDB_ARRAY_SIZE(new_kv_tbl)); /* check new data */ test_check_fdb_by_kvs(new_kv_tbl, FDB_ARRAY_SIZE(new_kv_tbl)); /* check old data */ test_check_fdb_by_kvs(old_kv_tbl, FDB_ARRAY_SIZE(old_kv_tbl)); } static void test_fdb_kvdb_set_default(void) { uassert_true(fdb_kv_set_default(&test_kvdb) == FDB_NO_ERR); } static rt_err_t utest_tc_init(void) { dir_delete(TEST_TS_PART_NAME); return RT_EOK; } static rt_err_t utest_tc_cleanup(void) { if (test_kvdb.parent.init_ok) { fdb_kv_set_default(&test_kvdb); fdb_kvdb_deinit(&test_kvdb); } return RT_EOK; } static void testcase(void) { UTEST_UNIT_RUN(test_fdb_kvdb_init); UTEST_UNIT_RUN(test_fdb_kvdb_init_check); UTEST_UNIT_RUN(test_fdb_create_kv_blob); UTEST_UNIT_RUN(test_fdb_change_kv_blob); UTEST_UNIT_RUN(test_fdb_del_kv_blob); UTEST_UNIT_RUN(test_fdb_create_kv); UTEST_UNIT_RUN(test_fdb_change_kv); UTEST_UNIT_RUN(test_fdb_del_kv); UTEST_UNIT_RUN(test_fdb_gc); UTEST_UNIT_RUN(test_fdb_scale_up); UTEST_UNIT_RUN(test_fdb_kvdb_set_default); UTEST_UNIT_RUN(test_fdb_kvdb_deinit); } UTEST_TC_EXPORT(testcase, "packages.system.flashdb.kvdb", utest_tc_init, utest_tc_cleanup, 20); #endif /* defined(RT_USING_UTEST) && defined(FDB_USING_KVDB) */