From 2ca6d2d4b4e9b0ce36ee6c7945550a67e43a0445 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Tue, 7 May 2024 15:31:38 +0200 Subject: [PATCH] feat(storage): add a test app for std::filesystem features --- tools/test_apps/storage/.build-test-rules.yml | 12 + .../storage/std_filesystem/CMakeLists.txt | 8 + .../storage/std_filesystem/README.md | 70 ++++++ .../std_filesystem/main/CMakeLists.txt | 10 + .../std_filesystem/main/idf_component.yml | 2 + .../std_filesystem/main/test_fs_image/file | 1 + .../main/test_fs_image/test_dir_iter/dir1/f1 | 0 .../test_fs_image/test_dir_iter/dir2/dir3/f3 | 0 .../storage/std_filesystem/main/test_ops.cpp | 196 ++++++++++++++++ .../std_filesystem/main/test_paths.cpp | 44 ++++ .../std_filesystem/main/test_status.cpp | 213 ++++++++++++++++++ .../main/test_std_filesystem_main.cpp | 25 ++ .../storage/std_filesystem/partitions.csv | 3 + .../std_filesystem/pytest_std_filesystem.py | 12 + .../storage/std_filesystem/sdkconfig.defaults | 14 ++ 15 files changed, 610 insertions(+) create mode 100644 tools/test_apps/storage/std_filesystem/CMakeLists.txt create mode 100644 tools/test_apps/storage/std_filesystem/README.md create mode 100644 tools/test_apps/storage/std_filesystem/main/CMakeLists.txt create mode 100644 tools/test_apps/storage/std_filesystem/main/idf_component.yml create mode 100644 tools/test_apps/storage/std_filesystem/main/test_fs_image/file create mode 100644 tools/test_apps/storage/std_filesystem/main/test_fs_image/test_dir_iter/dir1/f1 create mode 100644 tools/test_apps/storage/std_filesystem/main/test_fs_image/test_dir_iter/dir2/dir3/f3 create mode 100644 tools/test_apps/storage/std_filesystem/main/test_ops.cpp create mode 100644 tools/test_apps/storage/std_filesystem/main/test_paths.cpp create mode 100644 tools/test_apps/storage/std_filesystem/main/test_status.cpp create mode 100644 tools/test_apps/storage/std_filesystem/main/test_std_filesystem_main.cpp create mode 100644 tools/test_apps/storage/std_filesystem/partitions.csv create mode 100644 tools/test_apps/storage/std_filesystem/pytest_std_filesystem.py create mode 100644 tools/test_apps/storage/std_filesystem/sdkconfig.defaults diff --git a/tools/test_apps/storage/.build-test-rules.yml b/tools/test_apps/storage/.build-test-rules.yml index f0ab55097c..c98bb01b98 100644 --- a/tools/test_apps/storage/.build-test-rules.yml +++ b/tools/test_apps/storage/.build-test-rules.yml @@ -38,3 +38,15 @@ tools/test_apps/storage/sdmmc_console: - sdmmc - esp_driver_sdmmc - esp_driver_sdspi + +tools/test_apps/storage/std_filesystem: + enable: + - if: IDF_TARGET in ["esp32", "esp32c3"] + reason: one Xtensa and one RISC-V chip should be enough + disable: + - if: IDF_TOOLCHAIN == "clang" + reason: Issue with C++ exceptions on Xtensa, issue with getrandom linking on RISC-V + depends_components: + - vfs + - newlib + - fatfs diff --git a/tools/test_apps/storage/std_filesystem/CMakeLists.txt b/tools/test_apps/storage/std_filesystem/CMakeLists.txt new file mode 100644 index 0000000000..077a86bb3b --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") +project(std_filesystem_test) diff --git a/tools/test_apps/storage/std_filesystem/README.md b/tools/test_apps/storage/std_filesystem/README.md new file mode 100644 index 0000000000..226ee012d7 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/README.md @@ -0,0 +1,70 @@ +| Supported Targets | ESP32 | ESP32-C3 | +| ----------------- | ----- | -------- | + +This is a test app which verifies that std::filesystem features work in ESP-IDF. The tests are written using [Catch2](https://github.com/catchorg/Catch2) managed [component](https://components.espressif.com/components/espressif/catch2/). + +To run the tests: + +```shell +idf.py flash monitor +``` + +Or, in QEMU: + +```shell +idf.py qemu monitor +``` + +Or, using pytest: + +```shell +idf.py build +pytest --embedded-services idf,qemu --target esp32 --ignore managed_components +``` + +## Feature Support + +Please update `_cplusplus_filesystem` section in cplusplus.rst when modifying this table. + +| Feature | Supported | Tested | Comment | +|------------------------------|-----------|--------|---------------------------------------------------------------------------------------------------------------| +| absolute | y | y | | +| canonical | y | y | | +| weakly_canonical | y | y | | +| relative | y | y | | +| proximate | y | y | | +| copy | y | y | this function has complex behavior, not sure about test coverage | +| copy_file | y | y | | +| copy_symlink | n | n | symlinks are not supported | +| create_directory | y | y | | +| create_directories | y | y | | +| create_hard_link | n | n | hard links are not supported | +| create_symlink | n | n | symlinks are not supported | +| create_directory_symlink | n | n | symlinks are not supported | +| current_path | partial | y | setting path is not supported in IDF | +| exists | y | y | | +| equivalent | y | y | | +| file_size | y | y | | +| hard_link_count | n | n | hard links are not supported | +| last_write_time | y | y | | +| permissions | partial | y | setting permissions is not supported | +| read_symlink | n | n | symlinks are not supported | +| remove | y | y | | +| remove_all | y | y | | +| rename | y | y | | +| resize_file | n | y | doesn't work, toolchain has to be built with _GLIBCXX_HAVE_TRUNCATE | +| space | n | y | doesn't work, toolchain has to be built with _GLIBCXX_HAVE_SYS_STATVFS_H and statvfs function must be defined | +| status | y | y | | +| symlink_status | n | n | symlinks are not supported | +| temp_directory_path | y | y | works if /tmp directory has been mounted | +| directory_iterator | y | y | | +| recursive_directory_iterator | y | y | | +| is_block_file | y | y | | +| is_character_file | y | y | | +| is_directory | y | y | | +| is_empty | y | y | | +| is_fifo | y | y | | +| is_other | n | n | | +| is_regular_file | y | y | | +| is_socket | y | y | | +| is_symlink | y | y | | diff --git a/tools/test_apps/storage/std_filesystem/main/CMakeLists.txt b/tools/test_apps/storage/std_filesystem/main/CMakeLists.txt new file mode 100644 index 0000000000..d638f7f159 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register(SRCS + "test_std_filesystem_main.cpp" + "test_ops.cpp" + "test_paths.cpp" + "test_status.cpp" + INCLUDE_DIRS "." + PRIV_REQUIRES vfs fatfs + WHOLE_ARCHIVE) + +fatfs_create_spiflash_image(storage ${CMAKE_CURRENT_LIST_DIR}/test_fs_image FLASH_IN_PROJECT) diff --git a/tools/test_apps/storage/std_filesystem/main/idf_component.yml b/tools/test_apps/storage/std_filesystem/main/idf_component.yml new file mode 100644 index 0000000000..15668710d3 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + espressif/catch2: "^3.7.0" diff --git a/tools/test_apps/storage/std_filesystem/main/test_fs_image/file b/tools/test_apps/storage/std_filesystem/main/test_fs_image/file new file mode 100644 index 0000000000..a32a4347a4 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/test_fs_image/file @@ -0,0 +1 @@ +1234567890 diff --git a/tools/test_apps/storage/std_filesystem/main/test_fs_image/test_dir_iter/dir1/f1 b/tools/test_apps/storage/std_filesystem/main/test_fs_image/test_dir_iter/dir1/f1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/test_apps/storage/std_filesystem/main/test_fs_image/test_dir_iter/dir2/dir3/f3 b/tools/test_apps/storage/std_filesystem/main/test_fs_image/test_dir_iter/dir2/dir3/f3 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/test_apps/storage/std_filesystem/main/test_ops.cpp b/tools/test_apps/storage/std_filesystem/main/test_ops.cpp new file mode 100644 index 0000000000..12d63c5265 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/test_ops.cpp @@ -0,0 +1,196 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "esp_vfs_fat.h" +#include "wear_levelling.h" + +class OpsTest { +private: + wl_handle_t m_wl_handle; +public: + OpsTest() + { + esp_vfs_fat_mount_config_t mount_config = VFS_FAT_MOUNT_DEFAULT_CONFIG(); + + esp_err_t err = esp_vfs_fat_spiflash_mount_rw_wl("/test", "storage", &mount_config, &m_wl_handle); + if (err != ESP_OK) { + throw std::runtime_error("Failed to mount FAT filesystem"); + } + } + ~OpsTest() + { + esp_vfs_fat_spiflash_unmount_rw_wl("/test", m_wl_handle); + } + + void test_create_remove() + { + std::filesystem::create_directory("/test/dir"); + CHECK(std::filesystem::exists("/test/dir")); + + CHECK(std::filesystem::remove("/test/dir")); + CHECK(!std::filesystem::exists("/test/dir")); + } + + /* + The following two tests rely on the following directory structure + in the generated FAT filesystem: + + /test + └── test_dir_iter + ├── dir1 + │   └── f1 + └── dir2 + └── dir3 + └── f3 + */ + void test_directory_iterator() + { + std::filesystem::directory_iterator it("/test/test_dir_iter"); + CHECK(it != std::filesystem::directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir1"); + CHECK(it->is_directory()); + ++it; + CHECK(it != std::filesystem::directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir2"); + CHECK(it->is_directory()); + ++it; + CHECK(it == std::filesystem::directory_iterator()); + } + + void test_recursive_directory_iterator() + { + std::filesystem::recursive_directory_iterator it("/test/test_dir_iter"); + CHECK(it != std::filesystem::recursive_directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir1"); + CHECK(it->is_directory()); + ++it; + CHECK(it != std::filesystem::recursive_directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir1/f1"); + CHECK(it->is_regular_file()); + ++it; + CHECK(it != std::filesystem::recursive_directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir2"); + CHECK(it->is_directory()); + ++it; + CHECK(it != std::filesystem::recursive_directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir2/dir3"); + CHECK(it->is_directory()); + ++it; + CHECK(it != std::filesystem::recursive_directory_iterator()); + CHECK(it->path() == "/test/test_dir_iter/dir2/dir3/f3"); + CHECK(it->is_regular_file()); + ++it; + CHECK(it == std::filesystem::recursive_directory_iterator()); + } + + void test_copy_remove_recursive_copy() + { + if (std::filesystem::exists("/test/copy_dir")) { + CHECK(std::filesystem::remove_all("/test/copy_dir")); + } + + CHECK(std::filesystem::create_directory("/test/copy_dir")); + REQUIRE_NOTHROW(std::filesystem::copy("/test/test_dir_iter/dir1/f1", "/test/copy_dir/f1")); + CHECK(std::filesystem::exists("/test/copy_dir/f1")); + CHECK(std::filesystem::remove("/test/copy_dir/f1")); + CHECK(std::filesystem::remove("/test/copy_dir")); + + REQUIRE_NOTHROW(std::filesystem::copy("/test/test_dir_iter", "/test/copy_dir", std::filesystem::copy_options::recursive)); + CHECK(std::filesystem::exists("/test/copy_dir/dir1/f1")); + CHECK(std::filesystem::exists("/test/copy_dir/dir2/dir3/f3")); + CHECK(std::filesystem::remove_all("/test/copy_dir")); + } + + void test_create_directories() + { + if (std::filesystem::exists("/test/create_dir")) { + CHECK(std::filesystem::remove_all("/test/create_dir")); + } + CHECK(std::filesystem::create_directories("/test/create_dir/dir1/dir2")); + CHECK(std::filesystem::exists("/test/create_dir/dir1/dir2")); + CHECK(std::filesystem::remove_all("/test/create_dir")); + } + + void test_rename_file() + { + if (std::filesystem::exists("/test/rename_file")) { + CHECK(std::filesystem::remove("/test/rename_file")); + } + std::filesystem::create_directory("/test/rename_file"); + std::filesystem::copy_file("/test/file", "/test/rename_file/file"); + CHECK(std::filesystem::exists("/test/rename_file/file")); + std::filesystem::rename("/test/rename_file/file", "/test/rename_file/file2"); + CHECK(std::filesystem::exists("/test/rename_file/file2")); + CHECK(std::filesystem::remove_all("/test/rename_file")); + } + + void test_file_size_resize() + { + if (std::filesystem::exists("/test/file_size")) { + CHECK(std::filesystem::remove("/test/file_size")); + } + std::filesystem::copy_file("/test/file", "/test/file_size"); + CHECK(std::filesystem::file_size("/test/file_size") == 11); + // Not supported: libstdc++ has to be built with _GLIBCXX_HAVE_TRUNCATE + CHECK_THROWS(std::filesystem::resize_file("/test/file_size", 20)); + CHECK(std::filesystem::remove("/test/file_size")); + } + + void test_file_last_write_time() + { + if (std::filesystem::exists("/test/file_time")) { + CHECK(std::filesystem::remove("/test/file_time")); + } + std::filesystem::copy_file("/test/file", "/test/file_time"); + auto time = std::filesystem::last_write_time("/test/file_time"); + struct stat st = {}; + stat("/test/file_time", &st); + struct utimbuf times = {st.st_atime, st.st_mtime + 1000000000}; + utime("/test/file_time", ×); + + auto time2 = std::filesystem::last_write_time("/test/file_time"); + CHECK(time2 > time); + } + + void test_space() + { + // Not supported: libstdc++ has to be built with _GLIBCXX_HAVE_SYS_STATVFS_H and statvfs function + // has to be defined + CHECK_THROWS(std::filesystem::space("/test")); + } + + void test_permissions() + { + auto perm = std::filesystem::status("/test/file").permissions(); + CHECK(perm == std::filesystem::perms::all); + + std::filesystem::permissions("/test/file", std::filesystem::perms::owner_read, std::filesystem::perm_options::replace); + + // setting permissions is not supported and has no effect + perm = std::filesystem::status("/test/file").permissions(); + CHECK(perm == std::filesystem::perms::all); + } + + + // when adding a test method, don't forget to add it to the list below +}; + +METHOD_AS_TEST_CASE(OpsTest::test_create_remove, "Test create and remove directories"); +METHOD_AS_TEST_CASE(OpsTest::test_directory_iterator, "Test directory iterator"); +METHOD_AS_TEST_CASE(OpsTest::test_recursive_directory_iterator, "Test recursive directory iterator"); +METHOD_AS_TEST_CASE(OpsTest::test_copy_remove_recursive_copy, "Test copy, remove and recursive copy"); +METHOD_AS_TEST_CASE(OpsTest::test_create_directories, "Test create directories"); +METHOD_AS_TEST_CASE(OpsTest::test_rename_file, "Test rename file"); +METHOD_AS_TEST_CASE(OpsTest::test_file_size_resize, "Test file size and resize"); +METHOD_AS_TEST_CASE(OpsTest::test_file_last_write_time, "Test file last write time"); +METHOD_AS_TEST_CASE(OpsTest::test_space, "Test space"); +METHOD_AS_TEST_CASE(OpsTest::test_permissions, "Test permissions"); diff --git a/tools/test_apps/storage/std_filesystem/main/test_paths.cpp b/tools/test_apps/storage/std_filesystem/main/test_paths.cpp new file mode 100644 index 0000000000..92546ecb75 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/test_paths.cpp @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +TEST_CASE("std::filesystem path, relative, proximate, absolute") +{ + // In IDF, CWD is always in the the root directory + CHECK(std::filesystem::current_path() == "/"); + + // Create absolute path from relative path + std::filesystem::path rel_path("test/file.txt"); + std::filesystem::path abs_path = std::filesystem::absolute(rel_path); + CHECK(abs_path == "/test/file.txt"); + + // Create relative path from absolute path + std::filesystem::path rel_path2 = std::filesystem::relative(abs_path); + CHECK(rel_path2 == "test/file.txt"); + + // Create relative path from absolute path with different base + std::filesystem::path rel_path3 = std::filesystem::relative(abs_path, "/test"); + CHECK(rel_path3 == "file.txt"); + + std::filesystem::path prox_path = std::filesystem::proximate("/root1/file", "/root2"); + CHECK(prox_path == "../root1/file"); +} + +TEST_CASE("std::filesystem weakly_canonical") +{ + CHECK(std::filesystem::weakly_canonical("/a/b/c/./d/../e/f/../g") == "/a/b/c/e/g"); +} + +TEST_CASE("std::filesystem current_path") +{ + // In IDF, CWD is always in the the root directory + CHECK(std::filesystem::current_path() == "/"); + + // Changing the current path in IDF is not supported + CHECK_THROWS(std::filesystem::current_path("/test")); +} diff --git a/tools/test_apps/storage/std_filesystem/main/test_status.cpp b/tools/test_apps/storage/std_filesystem/main/test_status.cpp new file mode 100644 index 0000000000..7eeb0ce8f7 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/test_status.cpp @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_err.h" +#include + +/* Helper VFS driver to test std::filesystem */ + +typedef struct { + const char* cmp_path; + int ret_errno; + struct stat ret_stat; + DIR out_dir; + int n_dir_entries; + struct dirent* ret_dirent_array; +} test_vfs_ctx_t; + +static int test_vfs_open(void* ctx, const char* path, int flags, int mode) +{ + test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx; + if (strcmp(path, vfs_ctx->cmp_path) != 0) { + errno = vfs_ctx->ret_errno; + return -1; + } + return 0; +} + +static int test_vfs_stat(void* ctx, const char* path, struct stat* st) +{ + test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx; + if (strcmp(path, vfs_ctx->cmp_path) != 0) { + errno = vfs_ctx->ret_errno; + return -1; + } + *st = vfs_ctx->ret_stat; + return 0; +} + +static DIR* test_vfs_opendir(void* ctx, const char* name) +{ + test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx; + if (strcmp(name, vfs_ctx->cmp_path) != 0) { + errno = vfs_ctx->ret_errno; + return nullptr; + } + return &vfs_ctx->out_dir; +} + +static struct dirent* test_vfs_readdir(void* ctx, DIR* pdir) +{ + test_vfs_ctx_t* vfs_ctx = (test_vfs_ctx_t*)ctx; + if (vfs_ctx->ret_errno) { + errno = vfs_ctx->ret_errno; + return nullptr; + } + if (vfs_ctx->n_dir_entries == 0) { + return nullptr; + } + vfs_ctx->n_dir_entries--; + struct dirent* ret = &vfs_ctx->ret_dirent_array[0]; + vfs_ctx->ret_dirent_array++; + return ret; +} + +static int test_vfs_closedir(void* ctx, DIR* pdir) +{ + return 0; +} + +/* Actual test case starts here */ + +TEST_CASE("std::filesystem status functions") +{ + test_vfs_ctx_t test_ctx = {}; + esp_vfs_t desc = {}; + desc.flags = ESP_VFS_FLAG_CONTEXT_PTR; + desc.open_p = test_vfs_open; + desc.stat_p = test_vfs_stat; + desc.opendir_p = test_vfs_opendir; + desc.readdir_p = test_vfs_readdir; + desc.closedir_p = test_vfs_closedir; + + REQUIRE(esp_vfs_register("/test", &desc, &test_ctx) == ESP_OK); + + SECTION("Test file exists") { + test_ctx.cmp_path = "/file.txt"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFREG; + + CHECK(std::filesystem::exists("/test/file.txt")); + CHECK(std::filesystem::is_regular_file("/test/file.txt")); + } + + SECTION("Test directory exists") { + test_ctx.cmp_path = "/dir"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFDIR; + + CHECK(std::filesystem::exists("/test/dir")); + CHECK(std::filesystem::is_directory("/test/dir")); + } + + SECTION("Test non-existent file") { + test_ctx.cmp_path = ""; + test_ctx.ret_errno = ENOENT; + + CHECK(!std::filesystem::exists("/test/nonexistent")); + } + + SECTION("Test is_character_file") { + test_ctx.cmp_path = "/chardev"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFCHR; + + CHECK(std::filesystem::exists("/test/chardev")); + CHECK(std::filesystem::is_character_file("/test/chardev")); + } + + SECTION("Test is_block_file") { + test_ctx.cmp_path = "/blockdev"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFBLK; + + CHECK(std::filesystem::exists("/test/blockdev")); + CHECK(std::filesystem::is_block_file("/test/blockdev")); + } + + SECTION("Test is_fifo") { + test_ctx.cmp_path = "/fifo"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFIFO; + + CHECK(std::filesystem::exists("/test/fifo")); + CHECK(std::filesystem::is_fifo("/test/fifo")); + } + + SECTION("Test is_socket") { + test_ctx.cmp_path = "/socket"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFSOCK; + + CHECK(std::filesystem::exists("/test/socket")); + CHECK(std::filesystem::is_socket("/test/socket")); + } + + SECTION("Test is_symlink") { + test_ctx.cmp_path = "/symlink"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFLNK; + + CHECK(std::filesystem::exists("/test/symlink")); + CHECK(std::filesystem::is_symlink("/test/symlink")); + } + + SECTION("Test is_empty with file") { + test_ctx.cmp_path = "/file.txt"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFREG; + test_ctx.ret_stat.st_size = 10; + + CHECK(!std::filesystem::is_empty("/test/file.txt")); + + test_ctx.ret_stat.st_size = 0; + CHECK(std::filesystem::is_empty("/test/file.txt")); + } + + SECTION("Test is_empty with directory") { + test_ctx.cmp_path = "/dir"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFDIR; + + CHECK(std::filesystem::is_empty("/test/dir")); + } + + SECTION("Test is_empty with non-empty directory") { + test_ctx.cmp_path = "/dir"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFDIR; + test_ctx.n_dir_entries = 2; + struct dirent entries[2] = { + { .d_ino = 0, .d_type = DT_REG, .d_name = "foo" }, + { .d_ino = 0, .d_type = DT_REG, .d_name = "bar" }, + }; + test_ctx.ret_dirent_array = entries; + + CHECK(!std::filesystem::is_empty("/test/dir")); + } + + SECTION("directory_iterator, empty directory") { + test_ctx.cmp_path = "/dir"; + test_ctx.ret_stat = {}; + test_ctx.ret_stat.st_mode = S_IFDIR; + test_ctx.n_dir_entries = 0; + struct dirent entries[2] = { + { .d_ino = 0, .d_type = DT_REG, .d_name = "." }, + { .d_ino = 0, .d_type = DT_REG, .d_name = ".." }, + }; + test_ctx.ret_dirent_array = entries; + + CHECK(std::filesystem::directory_iterator("/test/dir") == std::filesystem::directory_iterator{}); + } + + CHECK(esp_vfs_unregister("/test") == ESP_OK); +} diff --git a/tools/test_apps/storage/std_filesystem/main/test_std_filesystem_main.cpp b/tools/test_apps/storage/std_filesystem/main/test_std_filesystem_main.cpp new file mode 100644 index 0000000000..23a32812a9 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/main/test_std_filesystem_main.cpp @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +extern "C" void app_main(void) +{ + const char *argv[] = { + "target_test_main", + "--durations", + "yes", + NULL + }; + int argc = sizeof(argv)/sizeof(argv[0]) - 1; + + auto result = Catch::Session().run(argc, argv); + if (result != 0) { + printf("Test failed with result %d\n", result); + } else { + printf("Test passed.\n"); + } +} diff --git a/tools/test_apps/storage/std_filesystem/partitions.csv b/tools/test_apps/storage/std_filesystem/partitions.csv new file mode 100644 index 0000000000..c4bcce8510 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/partitions.csv @@ -0,0 +1,3 @@ +# Name, Type, SubType, Offset, Size, Flags +factory, app, factory, 0x10000, 1400k, +storage, data, fat, , 528k, diff --git a/tools/test_apps/storage/std_filesystem/pytest_std_filesystem.py b/tools/test_apps/storage/std_filesystem/pytest_std_filesystem.py new file mode 100644 index 0000000000..82468d1e41 --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/pytest_std_filesystem.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.qemu +@pytest.mark.host_test +@pytest.mark.esp32 +@pytest.mark.esp32c3 +def test_std_filesystem(dut: Dut) -> None: + dut.expect_exact('All tests passed', timeout=200) diff --git a/tools/test_apps/storage/std_filesystem/sdkconfig.defaults b/tools/test_apps/storage/std_filesystem/sdkconfig.defaults new file mode 100644 index 0000000000..1c55b3519d --- /dev/null +++ b/tools/test_apps/storage/std_filesystem/sdkconfig.defaults @@ -0,0 +1,14 @@ +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_FATFS_LFN_HEAP=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=n +CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES=y +CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y +CONFIG_LOG_DEFAULT_LEVEL_WARN=y +CONFIG_LOG_MAXIMUM_LEVEL_INFO=y