Files

135 lines
5.0 KiB
C++

#pragma once
#include <algorithm>
#include <cassert>
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <sys/stat.h>
class FakeFilesystem;
struct Stream {
FakeFilesystem* fs;
std::string path;
bool writer, closed = false;
size_t position = 0, buffer_size = 4096;
std::vector<uint8_t> buffer;
Stream(FakeFilesystem*, const char*, bool);
void close();
~Stream() { close(); }
};
class File {
std::shared_ptr<Stream> 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<std::string, std::vector<uint8_t>> 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<uint8_t>& 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<Stream>(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; }