#pragma once #include #include #include #include #include #include #include #include #include #include #include class FakeFilesystem; struct Stream { FakeFilesystem* fs; std::string path; bool writer, closed = false; size_t position = 0, buffer_size = 4096; std::vector buffer; Stream(FakeFilesystem*, const char*, bool); void close(); ~Stream() { close(); } }; class File { std::shared_ptr stream; public: File() = default; File(FakeFilesystem*, const char*, bool); explicit operator bool() const { return stream && !stream->closed; } bool setBufferSize(size_t); size_t write(const uint8_t*, size_t); size_t read(uint8_t*, size_t); size_t size(); void close() { if (stream) stream->close(); stream.reset(); } }; class FakeFilesystem { public: std::map> files; unsigned operations = 0, handles = 0, backend_writes = 0; unsigned rename_calls = 0, remove_calls = 0, config_calls = 0; unsigned metadata_calls = 0, fail_metadata_at = 0; unsigned fail_rename = 0, fail_remove = 0, fail_config = 0; bool fail_metadata = false, fail_open = false, partial_open = false; bool fail_read = false, short_write = false, fail_close = false, fail_format = false; size_t max_backend_write = 0; bool exists(const char* path) const { return files.count(path) != 0; } bool mkdir(const char*) { ++operations; return true; } bool format() { ++operations; if (fail_format) return false; files.clear(); return true; } File open(const char* path, const char* mode = "r", bool = false) { ++operations; bool writer = mode[0] == 'w'; if (fail_open) { if (writer && partial_open) files[path].clear(); return File(); } if (!writer && !exists(path)) return File(); if (writer) files[path].clear(); return File(this, path, writer); } bool rename(const char* from, const char* to) { ++operations; ++rename_calls; if (rename_calls == fail_rename || !exists(from) || exists(to)) return false; files[to] = files.at(from); files.erase(from); return true; } bool remove(const char* path) { ++operations; ++remove_calls; assert(exists(path) && "No VFS log for normal absence"); if (remove_calls == fail_remove) return false; files.erase(path); return true; } void backend(const std::string& path, const std::vector& bytes) { if (bytes.empty()) return; ++backend_writes; max_backend_write = std::max(max_backend_write, bytes.size()); files[path].insert(files[path].end(), bytes.begin(), bytes.end()); } }; inline Stream::Stream(FakeFilesystem* f, const char* p, bool w) : fs(f), path(p), writer(w) { ++fs->handles; } inline void Stream::close() { if (closed) return; ++fs->operations; if (writer && !fs->fail_close) fs->backend(path, buffer); buffer.clear(); closed = true; --fs->handles; } inline File::File(FakeFilesystem* fs, const char* path, bool writer) : stream(std::make_shared(fs, path, writer)) {} inline bool File::setBufferSize(size_t length) { assert(stream && stream->position == 0); ++stream->fs->operations; ++stream->fs->config_calls; if (stream->fs->config_calls == stream->fs->fail_config) return false; stream->buffer_size = length; return true; } inline size_t File::write(const uint8_t* data, size_t length) { assert(stream && stream->writer); ++stream->fs->operations; if (stream->fs->short_write && length) --length; size_t consumed = 0; while (consumed < length) { if (stream->buffer.size() == stream->buffer_size) { stream->fs->backend(stream->path, stream->buffer); stream->buffer.clear(); } size_t n = std::min(length-consumed, stream->buffer_size-stream->buffer.size()); stream->buffer.insert(stream->buffer.end(), data+consumed, data+consumed+n); consumed += n; if (stream->buffer.size() == stream->buffer_size) { stream->fs->backend(stream->path, stream->buffer); stream->buffer.clear(); } } stream->position += length; return length; } inline size_t File::read(uint8_t* data, size_t length) { assert(stream && !stream->writer); ++stream->fs->operations; if (stream->fs->fail_read) return 0; const auto& bytes = stream->fs->files.at(stream->path); size_t n = std::min(length, bytes.size()-std::min(stream->position, bytes.size())); if (n) memcpy(data, bytes.data()+stream->position, n); stream->position += n; return n; } inline size_t File::size() { assert(stream); ++stream->fs->operations; return stream->fs->files.at(stream->path).size(); } inline int fixtureStat(FakeFilesystem* fs, const char* path, struct stat*) { ++fs->operations; ++fs->metadata_calls; if (fs->fail_metadata || fs->metadata_calls == fs->fail_metadata_at) { errno = EIO; return -1; } assert(strncmp(path, "/spiffs", 7) == 0); if (!fs->exists(path+7)) { errno = ENOENT; return -1; } return 0; } namespace fs { using SPIFFSFS = FakeFilesystem; } using esp_err_t = int; constexpr int ESP_OK = 0; inline int nvs_flash_erase() { return ESP_OK; }