fd/line: Add line buffered reader
This gives us similar functionality to std::getline but on stdplus::Fd
instances. Also more predictable behavior around newlines at the end of
a file.
Change-Id: I19039c15fa02019e4ad767ca8a1996a8417fdd86
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/test/fd/line.cpp b/test/fd/line.cpp
new file mode 100644
index 0000000..8d9eb32
--- /dev/null
+++ b/test/fd/line.cpp
@@ -0,0 +1,98 @@
+#include <gtest/gtest.h>
+#include <stdplus/exception.hpp>
+#include <stdplus/fd/gmock.hpp>
+#include <stdplus/fd/line.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/raw.hpp>
+#include <stdplus/util/cexec.hpp>
+#include <string_view>
+#include <sys/mman.h>
+
+namespace stdplus
+{
+namespace fd
+{
+
+ManagedFd makeMemfd(std::string_view contents)
+{
+ auto fd = ManagedFd(CHECK_ERRNO(memfd_create("test", 0), "memfd_create"));
+ write(fd, contents);
+ lseek(fd, 0, Whence::Set);
+ return fd;
+}
+
+TEST(LineReader, Empty)
+{
+ auto fd = makeMemfd("");
+ LineReader reader(fd);
+ EXPECT_EQ("", *reader.readLine());
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+}
+
+TEST(LineReader, SingleLine)
+{
+ auto fd = makeMemfd("\n");
+ LineReader reader(fd);
+ EXPECT_EQ("", *reader.readLine());
+ EXPECT_EQ("", *reader.readLine());
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+}
+
+TEST(LineReader, SomeData)
+{
+ auto fd = makeMemfd("A\nbcd\n\ne");
+ LineReader reader(fd);
+ EXPECT_EQ("A", *reader.readLine());
+ EXPECT_EQ("bcd", *reader.readLine());
+ EXPECT_EQ("", *reader.readLine());
+ EXPECT_EQ("e", *reader.readLine());
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+}
+
+TEST(LineReader, LargerThanBuf)
+{
+ std::string big(LineReader::buf_size + 10, 'a');
+ auto fd = makeMemfd(std::string("alpha\n") + big + "\ndef");
+ LineReader reader(fd);
+ EXPECT_EQ("alpha", *reader.readLine());
+ EXPECT_EQ(big, *reader.readLine());
+ EXPECT_EQ("def", *reader.readLine());
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+}
+
+using testing::_;
+
+inline auto readSv(std::string_view s)
+{
+ return [s](std::span<std::byte> buf) {
+ memcpy(buf.data(), s.data(), s.size());
+ return buf.subspan(0, s.size());
+ };
+}
+
+TEST(LineReader, Nonblock)
+{
+ FdMock fd;
+ {
+ testing::InSequence seq;
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("alph"));
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv({}));
+ EXPECT_CALL(fd, read(_)).WillOnce(readSv("a"));
+ EXPECT_CALL(fd, read(_))
+ .WillRepeatedly(testing::Throw(stdplus::exception::Eof("test")));
+ }
+
+ LineReader reader(fd);
+ EXPECT_EQ(nullptr, reader.readLine());
+ EXPECT_EQ("alpha", *reader.readLine());
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+ EXPECT_THROW(reader.readLine(), exception::Eof);
+}
+
+} // namespace fd
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index 4ebf17f..a0be742 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -33,6 +33,7 @@
'fd/managed',
'fd/intf',
'fd/impl',
+ 'fd/line',
'fd/mmap',
'fd/mock',
'fd/ops',