mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 17:19:09 -04:00
Merge branch 'feature/std_filesystem_supported_v5.4' into 'release/v5.4'
storage: make std::filesystem supported (v5.4) See merge request espressif/esp-idf!35430
This commit is contained in:
commit
bd0fc67f13
@ -39,7 +39,7 @@ typedef struct {
|
||||
FATFS fs; /* fatfs library FS structure */
|
||||
char tmp_path_buf[FILENAME_MAX+3]; /* temporary buffer used to prepend drive name to the path */
|
||||
char tmp_path_buf2[FILENAME_MAX+3]; /* as above; used in functions which take two path arguments */
|
||||
bool *o_append; /* O_APPEND is stored here for each max_files entries (because O_APPEND is not compatible with FA_OPEN_APPEND) */
|
||||
uint32_t *flags; /* file descriptor flags, array of max_files size */
|
||||
#ifdef CONFIG_VFS_SUPPORT_DIR
|
||||
char dir_path[FILENAME_MAX]; /* variable to store path of opened directory*/
|
||||
struct cached_data cached_fileinfo;
|
||||
@ -85,6 +85,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode);
|
||||
static int vfs_fat_close(void* ctx, int fd);
|
||||
static int vfs_fat_fstat(void* ctx, int fd, struct stat * st);
|
||||
static int vfs_fat_fsync(void* ctx, int fd);
|
||||
static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg);
|
||||
#ifdef CONFIG_VFS_SUPPORT_DIR
|
||||
static int vfs_fat_stat(void* ctx, const char * path, struct stat * st);
|
||||
static int vfs_fat_link(void* ctx, const char* n1, const char* n2);
|
||||
@ -170,6 +171,7 @@ static const esp_vfs_fs_ops_t s_vfs_fat = {
|
||||
.open_p = &vfs_fat_open,
|
||||
.close_p = &vfs_fat_close,
|
||||
.fstat_p = &vfs_fat_fstat,
|
||||
.fcntl_p = &vfs_fat_fcntl,
|
||||
.fsync_p = &vfs_fat_fsync,
|
||||
#ifdef CONFIG_VFS_SUPPORT_DIR
|
||||
.dir = &s_vfs_fat_dir,
|
||||
@ -199,19 +201,19 @@ esp_err_t esp_vfs_fat_register_cfg(const esp_vfs_fat_conf_t* conf, FATFS** out_f
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memset(fat_ctx, 0, ctx_size);
|
||||
fat_ctx->o_append = ff_memalloc(max_files * sizeof(bool));
|
||||
if (fat_ctx->o_append == NULL) {
|
||||
fat_ctx->flags = ff_memalloc(max_files * sizeof(*fat_ctx->flags));
|
||||
if (fat_ctx->flags == NULL) {
|
||||
free(fat_ctx);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
memset(fat_ctx->o_append, 0, max_files * sizeof(bool));
|
||||
memset(fat_ctx->flags, 0, max_files * sizeof(*fat_ctx->flags));
|
||||
fat_ctx->max_files = max_files;
|
||||
strlcpy(fat_ctx->fat_drive, conf->fat_drive, sizeof(fat_ctx->fat_drive) - 1);
|
||||
strlcpy(fat_ctx->base_path, conf->base_path, sizeof(fat_ctx->base_path) - 1);
|
||||
|
||||
esp_err_t err = esp_vfs_register_fs(conf->base_path, &s_vfs_fat, ESP_VFS_FLAG_CONTEXT_PTR | ESP_VFS_FLAG_STATIC, fat_ctx);
|
||||
if (err != ESP_OK) {
|
||||
free(fat_ctx->o_append);
|
||||
free(fat_ctx->flags);
|
||||
free(fat_ctx);
|
||||
return err;
|
||||
}
|
||||
@ -239,7 +241,7 @@ esp_err_t esp_vfs_fat_unregister_path(const char* base_path)
|
||||
return err;
|
||||
}
|
||||
_lock_close(&fat_ctx->lock);
|
||||
free(fat_ctx->o_append);
|
||||
free(fat_ctx->flags);
|
||||
free(fat_ctx);
|
||||
s_fat_ctxs[ctx] = NULL;
|
||||
return ESP_OK;
|
||||
@ -427,7 +429,7 @@ static int vfs_fat_open(void* ctx, const char * path, int flags, int mode)
|
||||
// Other VFS drivers handles O_APPEND well (to the best of my knowledge),
|
||||
// therefore this flag is stored here (at this VFS level) in order to save
|
||||
// memory.
|
||||
fat_ctx->o_append[fd] = (flags & O_APPEND) == O_APPEND;
|
||||
fat_ctx->flags[fd] = (flags & (O_APPEND | O_ACCMODE));
|
||||
_lock_release(&fat_ctx->lock);
|
||||
return fd;
|
||||
}
|
||||
@ -438,7 +440,7 @@ static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
|
||||
FIL* file = &fat_ctx->files[fd];
|
||||
FRESULT res;
|
||||
_lock_acquire(&fat_ctx->lock);
|
||||
if (fat_ctx->o_append[fd]) {
|
||||
if (fat_ctx->flags[fd] & O_APPEND) {
|
||||
if ((res = f_lseek(file, f_size(file))) != FR_OK) {
|
||||
ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
|
||||
errno = fresult_to_errno(res);
|
||||
@ -672,6 +674,26 @@ static int vfs_fat_fstat(void* ctx, int fd, struct stat * st)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vfs_fat_fcntl(void* ctx, int fd, int cmd, int arg)
|
||||
{
|
||||
vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
|
||||
switch (cmd) {
|
||||
case F_GETFL:
|
||||
return fat_ctx->flags[fd];
|
||||
case F_SETFL:
|
||||
fat_ctx->flags[fd] = arg;
|
||||
return 0;
|
||||
// no-ops:
|
||||
case F_SETLK:
|
||||
case F_SETLKW:
|
||||
case F_GETLK:
|
||||
return 0;
|
||||
default:
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VFS_SUPPORT_DIR
|
||||
|
||||
static inline mode_t get_stat_mode(bool is_dir)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -9,6 +9,7 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
/* realpath logic:
|
||||
@ -122,3 +123,20 @@ int chdir(const char *path)
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* std::filesystem functions call chmod and exit with an exception if it fails,
|
||||
* so not failing with ENOSYS seems a better solution.
|
||||
*/
|
||||
int chmod(const char *path, mode_t mode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* As a workaround for libstdc++ being built with _GLIBCXX_HAVE_DIRFD,
|
||||
* we have to provide at least a stub for dirfd function.
|
||||
*/
|
||||
int dirfd(DIR *dirp)
|
||||
{
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char *TAG = "sysconf";
|
||||
|
||||
#ifdef CONFIG_FREERTOS_UNICORE
|
||||
#define CPU_NUM 1
|
||||
#else
|
||||
@ -25,3 +30,24 @@ long sysconf(int arg)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// pathconf
|
||||
long fpathconf(int fildes, int name)
|
||||
{
|
||||
if (name == _PC_PATH_MAX) {
|
||||
return PATH_MAX;
|
||||
}
|
||||
ESP_LOGW(TAG, "fpathconf: unsupported name %d", name);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
long pathconf(const char *path, int name)
|
||||
{
|
||||
if (name == _PC_PATH_MAX) {
|
||||
return PATH_MAX;
|
||||
}
|
||||
ESP_LOGW(TAG, "pathconf: unsupported name %d", name);
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ The following C++ features are supported:
|
||||
- :ref:`cplusplus_multithreading`
|
||||
- :ref:`cplusplus_rtti`
|
||||
- :doc:`thread-local-storage` (``thread_local`` keyword)
|
||||
- :ref:`cplusplus_filesystem`
|
||||
- All C++ features implemented by GCC, except for some :ref:`cplusplus_limitations`. See `GCC documentation <https://gcc.gnu.org/projects/cxx-status.html>`_ for details on features implemented by GCC.
|
||||
|
||||
|
||||
@ -94,6 +95,21 @@ Enabling this option compiles all C++ files with RTTI support enabled, which all
|
||||
|
||||
See :example:`cxx/rtti` for an example of using RTTI in ESP-IDF. Specifically, this example demonstrates how to use the RTTI feature in ESP-IDF, enabling compile time support for RTTI, and showing how to print demangled type names of objects and functions, and how dynamic_cast behaves with objects of two classes derived from a common base class.
|
||||
|
||||
.. _cplusplus_filesystem:
|
||||
|
||||
Filesystem Library
|
||||
------------------
|
||||
|
||||
C++ Filesystem library (``#include <filesystem>``) features are supported in ESP-IDF, with the following exceptions:
|
||||
|
||||
- Since symbolic and hard links are not supported in ESP-IDF, related functions are not implemented.
|
||||
- ``std::filesystem::space`` is not implemented.
|
||||
- ``std::filesystem::resize_file`` is not implemented.
|
||||
- ``std::filesystem::current_path`` always returns ``/``. Setting the current path is not supported.
|
||||
- ``std::filesystem::permissions`` doesn't support setting file permissions.
|
||||
|
||||
Note that the choice of the filesystem also affects the behavior of the filesystem library. For example, SPIFFS filesystem has limited support for directories, therefore the related std::filesystem functions may not work as they do on a filesystem which does support directories.
|
||||
|
||||
Developing in C++
|
||||
-----------------
|
||||
|
||||
@ -186,7 +202,6 @@ Limitations
|
||||
- Linker script generator does not support function level placements for functions with C++ linkage.
|
||||
- Various section attributes (such as ``IRAM_ATTR``) are ignored when used with template functions.
|
||||
- Vtables are placed into Flash and are not accessible when the flash cache is disabled. Therefore, virtual function calls should be avoided in :ref:`iram-safe-interrupt-handlers`. Placement of Vtables cannot be adjusted using the linker script generator, yet.
|
||||
- C++ filesystem (``std::filesystem``) features are not supported.
|
||||
|
||||
|
||||
What to Avoid
|
||||
|
@ -13,6 +13,7 @@ ESP-IDF 支持以下 C++ 功能:
|
||||
- :ref:`cplusplus_multithreading`
|
||||
- :ref:`cplusplus_rtti`
|
||||
- :doc:`thread-local-storage` (``thread_local`` 关键字)
|
||||
- :ref:`cplusplus_filesystem`
|
||||
- 除部分 :ref:`cplusplus_limitations`,所有由 GCC 实现的 C++ 功能均受支持。有关由 GCC 所实现功能的详细信息,请参阅 `GCC 文档 <https://gcc.gnu.org/projects/cxx-status.html>`_。
|
||||
|
||||
|
||||
@ -94,6 +95,21 @@ ESP-IDF 默认禁用对 RTTI 的支持,可以用 :ref:`CONFIG_COMPILER_CXX_RTT
|
||||
|
||||
有关在 ESP-IDF 中使用 RTTI 的示例,请参阅 :example:`cxx/rtti`。该示例演示了如何在 ESP-IDF 中使用 RTTI 功能,启用编译时对 RTTI 的支持,并展示了如何打印对象和函数的去混淆类型名称,以及 dynamic_cast 在两个继承自同一基类的对象上如何表现。
|
||||
|
||||
.. _cplusplus_filesystem:
|
||||
|
||||
文件系统库
|
||||
----------
|
||||
|
||||
ESP-IDF 支持 C++ 文件系统库 (``#include <filesystem>``),但有部分功能尚未实现:
|
||||
|
||||
- 由于 ESP-IDF 不支持符号链接和硬链接,因此相关函数未实现。
|
||||
- 未实现 ``std::filesystem::space``。
|
||||
- 未实现 ``std::filesystem::resize_file``。
|
||||
- ``std::filesystem::current_path`` 只返回 ``/``。不支持设置当前路径。
|
||||
- ``std::filesystem::permissions`` 不支持设置文件权限。
|
||||
|
||||
请注意,文件系统的选择也会影响文件系统库的行为。例如,SPIFFS 文件系统对目录的支持有限,因此相关的 std::filesystem 函数可能无法像在支持目录的文件系统上那样正常工作。
|
||||
|
||||
在 C++ 中进行开发
|
||||
-----------------
|
||||
|
||||
@ -186,7 +202,6 @@ ESP-IDF 支持 ``iostream`` 功能,但应注意:
|
||||
- 链接脚本生成器不支持将具有 C++ 链接的函数单独放置在内存的特定位置。
|
||||
- 当与模板函数一起使用时,会忽略各种节属性(例如 ``IRAM_ATTR``)。
|
||||
- vtable 位于 flash 中,在禁用 flash 缓存时无法访问。因此,在 :ref:`iram-safe-interrupt-handlers` 中应避免调用虚拟函数。目前尚无法使用链接器脚本生成器调整 vtable 的放置位置。
|
||||
- 不支持 C++ 文件系统 (``std::filesystem``) 功能。
|
||||
|
||||
|
||||
注意事项
|
||||
|
@ -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
|
||||
|
8
tools/test_apps/storage/std_filesystem/CMakeLists.txt
Normal file
8
tools/test_apps/storage/std_filesystem/CMakeLists.txt
Normal file
@ -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)
|
70
tools/test_apps/storage/std_filesystem/README.md
Normal file
70
tools/test_apps/storage/std_filesystem/README.md
Normal file
@ -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 | |
|
10
tools/test_apps/storage/std_filesystem/main/CMakeLists.txt
Normal file
10
tools/test_apps/storage/std_filesystem/main/CMakeLists.txt
Normal file
@ -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)
|
@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/catch2: "^3.7.0"
|
@ -0,0 +1 @@
|
||||
1234567890
|
196
tools/test_apps/storage/std_filesystem/main/test_ops.cpp
Normal file
196
tools/test_apps/storage/std_filesystem/main/test_ops.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utime.h>
|
||||
#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");
|
44
tools/test_apps/storage/std_filesystem/main/test_paths.cpp
Normal file
44
tools/test_apps/storage/std_filesystem/main/test_paths.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <filesystem>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
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"));
|
||||
}
|
213
tools/test_apps/storage/std_filesystem/main/test_status.cpp
Normal file
213
tools/test_apps/storage/std_filesystem/main/test_status.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/dirent.h>
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_err.h"
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
/* 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);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <catch2/catch_session.hpp>
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
3
tools/test_apps/storage/std_filesystem/partitions.csv
Normal file
3
tools/test_apps/storage/std_filesystem/partitions.csv
Normal file
@ -0,0 +1,3 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
factory, app, factory, 0x10000, 1400k,
|
||||
storage, data, fat, , 528k,
|
|
@ -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)
|
14
tools/test_apps/storage/std_filesystem/sdkconfig.defaults
Normal file
14
tools/test_apps/storage/std_filesystem/sdkconfig.defaults
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user