Add file reader (#1155)

* Add file reader
* Add a simple test example of parsing settings.
* Use new FileLineReader to parse Glass presets
* Trim CRLF from Glass preset name
This commit is contained in:
Kyle Reed 2023-06-15 00:45:13 -07:00 committed by GitHub
parent a5c7eb2fbc
commit 34fefd1cad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 659 additions and 325 deletions

View file

@ -22,267 +22,7 @@
#include "doctest.h"
#include "file.hpp"
#include "file_wrapper.hpp"
#include <cstring>
#include <cstdio>
#include <string>
/* Mocks the File interface with a backing string. */
class MockFile {
public:
using Error = File::Error;
using Offset = File::Offset;
using Size = File::Size;
template <typename T>
using Result = File::Result<T>;
MockFile(std::string data)
: data_{std::move(data)} {}
Size size() { return data_.size(); }
Result<Offset> seek(uint32_t offset) {
if ((int32_t)offset < 0)
return {static_cast<Error>(FR_BAD_SEEK)};
auto previous = offset_;
if (offset > size())
data_.resize(offset);
offset_ = offset;
return previous;
}
Result<Offset> truncate() {
data_.resize(offset_);
return offset_;
}
Result<Size> read(void* data, Size bytes_to_read) {
if (offset_ + bytes_to_read > size())
bytes_to_read = size() - offset_;
if (bytes_to_read == 0 || bytes_to_read > size()) // NB: underflow wrap
return 0;
memcpy(data, &data_[offset_], bytes_to_read);
offset_ += bytes_to_read;
return bytes_to_read;
}
Result<Size> write(const void* data, Size bytes_to_write) {
auto new_offset = offset_ + bytes_to_write;
if (new_offset >= size())
data_.resize(new_offset);
memcpy(&data_[offset_], data, bytes_to_write);
offset_ = new_offset;
return bytes_to_write;
}
Optional<Error> sync() {
return {};
}
std::string data_;
uint32_t offset_{0};
};
/* Verifies correctness of MockFile. */
TEST_SUITE("Test MockFile") {
SCENARIO("File size") {
GIVEN("Empty string") {
MockFile f{""};
THEN("size() should be 0.") {
CHECK_EQ(f.size(), 0);
}
}
GIVEN("Not empty string") {
MockFile f{"abc"};
THEN("size() should be string length.") {
CHECK_EQ(f.size(), 3);
}
}
}
SCENARIO("File seek") {
GIVEN("Valid file") {
MockFile f{"abc\ndef"};
auto init_size = f.size();
WHEN("seek()") {
f.seek(4);
THEN("offset_ should be updated.") {
CHECK_EQ(f.offset_, 4);
}
}
WHEN("seek() negative offset") {
auto r = f.seek(-1);
THEN("Result should be bad_seek.") {
CHECK(r.is_error());
CHECK_EQ(r.error().code(), FR_BAD_SEEK);
}
}
WHEN("seek() offset is size()") {
auto r = f.seek(f.size());
THEN("File should not grow.") {
CHECK(r.is_ok());
CHECK_EQ(f.size(), init_size);
}
}
WHEN("seek() offset > size()") {
auto r = f.seek(f.size() + 1);
THEN("File should grow.") {
CHECK(r.is_ok());
CHECK_EQ(f.size(), init_size + 1);
}
}
WHEN("seek() offset < size()") {
auto r = f.seek(1);
THEN("Result should be ok.") {
CHECK(r.is_ok());
}
r = f.seek(3);
THEN("Result should be previous offset") {
CHECK(r);
CHECK_EQ(*r, 1);
}
}
}
}
SCENARIO("File read") {
GIVEN("Valid file") {
MockFile f{"abc\ndef"};
const auto buf_len = 10;
std::string buf;
buf.resize(buf_len);
WHEN("Reading") {
auto r = f.read(&buf[0], 3);
THEN("Result should be number of bytes read") {
CHECK(r);
CHECK_EQ(*r, 3);
}
buf.resize(*r);
THEN("Buffer should contain read data") {
CHECK_EQ(buf.length(), 3);
CHECK_EQ(buf, "abc");
}
r = f.read(&buf[0], 3);
THEN("Reading should continue where it left off") {
CHECK_EQ(buf.length(), 3);
CHECK_EQ(buf, "\nde");
}
r = f.read(&buf[0], 3);
THEN("Reading should stop at the end of the file") {
CHECK(r);
CHECK_EQ(*r, 1);
buf.resize(*r);
CHECK_EQ(buf.length(), 1);
CHECK_EQ(buf, "f");
}
}
WHEN("Reading block larger than file size") {
auto r = f.read(&buf[0], buf_len);
buf.resize(*r);
THEN("It should read to file end.") {
CHECK(r);
CHECK_EQ(*r, 7);
CHECK_EQ(buf, f.data_);
}
}
}
}
SCENARIO("File write") {
GIVEN("Valid file") {
MockFile f{"abc\ndef"};
WHEN("Writing over existing region") {
f.write("xyz", 3);
THEN("It should overwrite") {
CHECK_EQ(f.data_, "xyz\ndef");
}
}
WHEN("Writing over past end") {
f.seek(f.size());
f.write("xyz", 3);
THEN("It should extend file and write") {
CHECK_EQ(f.size(), 10);
CHECK_EQ(f.data_, "abc\ndefxyz");
}
}
}
}
// This scenario was tested on device.
SCENARIO("File truncate") {
GIVEN("Valid file") {
MockFile f{"hello world"};
WHEN("truncating at offset 5") {
f.seek(5);
f.truncate();
THEN("resulting file should be 'hello'.") {
CHECK_EQ(f.size(), 5);
CHECK_EQ(f.data_, "hello");
}
}
}
}
SCENARIO("File truncate") {
GIVEN("Valid file") {
MockFile f{"abc\ndef"};
auto init_size = f.size();
WHEN("R/W pointer at end") {
f.seek(f.size());
f.truncate();
THEN("It should not change size.") {
CHECK_EQ(f.size(), init_size);
}
}
WHEN("R/W pointer in middle") {
f.seek(3);
f.truncate();
THEN("It should change size.") {
CHECK_EQ(f.size(), 3);
}
}
}
}
}
#include "mock_file.hpp"
TEST_SUITE_BEGIN("Test BufferWrapper");