From c87f9627efc8b612eb9b000acfcc6731cad15765 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 7 Oct 2025 09:12:22 +0200 Subject: Initial commit --- test/args.cc | 412 +++++++++++++++++++++++++++++++++++++++++++++++++ test/buffer.cc | 270 ++++++++++++++++++++++++++++++++ test/io.cc | 203 ++++++++++++++++++++++++ test/io_test_helper.cc | 82 ++++++++++ test/io_test_helper.hh | 18 +++ test/line.cc | 182 ++++++++++++++++++++++ test/str.cc | 67 ++++++++ 7 files changed, 1234 insertions(+) create mode 100644 test/args.cc create mode 100644 test/buffer.cc create mode 100644 test/io.cc create mode 100644 test/io_test_helper.cc create mode 100644 test/io_test_helper.hh create mode 100644 test/line.cc create mode 100644 test/str.cc (limited to 'test') diff --git a/test/args.cc b/test/args.cc new file mode 100644 index 0000000..22159d2 --- /dev/null +++ b/test/args.cc @@ -0,0 +1,412 @@ +#include "args.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SETUP_OPTIONS(args) \ + auto short_only = (args)->option('a', "", "an option"); \ + auto short_long = (args)->option('b', "bold", "set font style to bold"); \ + auto long_only = (args)->option("cold", "use if it is cold outside"); \ + auto short_only_req = (args)->option_argument('d', "", "", "distance"); \ + auto short_long_req = (args)->option_argument( \ + 'e', "eat", "FOOD", "what to order, what to eat?"); \ + auto long_only_req = \ + (args)->option_argument("form", "", "circle, shape or something else?"); \ + auto short_only_opt = (args)->option_argument('g', "", "", "", false); \ + auto short_long_opt = (args)->option_argument('h', "hold", "", "", false); \ + auto long_only_opt = (args)->option_argument("invert", "", "", false); + +namespace { + +class Arguments { + public: + [[nodiscard]] int c() const { return static_cast(str_.size()); }; + [[nodiscard]] char** v() const { return ptr_.get(); } + + explicit Arguments(std::vector str) : str_(std::move(str)) { + ptr_ = std::make_unique(str_.size()); + for (size_t i = 0; i < str_.size(); ++i) { + ptr_[i] = const_cast(str_[i].c_str()); + } + } + + Arguments(Arguments const&) = delete; + Arguments& operator=(Arguments const&) = delete; + + private: + std::unique_ptr ptr_; + std::vector str_; +}; + +class Builder { + public: + Arguments build() { return Arguments(std::move(str_)); } + + Builder& add(std::string str) { + str_.emplace_back(std::move(str)); + return *this; + } + + private: + std::vector str_; +}; + +} // namespace + +TEST(args, empty) { + auto args = Args::create(); + EXPECT_TRUE(args->run(0, nullptr)); + { + std::stringstream ss; + args->print_error(ss); + EXPECT_EQ("", ss.str()); + } + { + std::stringstream ss; + args->print_help(ss); + EXPECT_EQ("", ss.str()); + } +} + +TEST(args, options_not_set) { + auto args = Args::create(); + SETUP_OPTIONS(args); + EXPECT_TRUE(args->run(0, nullptr)); + EXPECT_FALSE(short_only->is_set()); + EXPECT_FALSE(short_long->is_set()); + EXPECT_FALSE(long_only->is_set()); + EXPECT_FALSE(short_only_req->is_set()); + EXPECT_FALSE(short_long_req->is_set()); + EXPECT_FALSE(long_only_req->is_set()); + EXPECT_FALSE(short_only_opt->is_set()); + EXPECT_FALSE(short_long_opt->is_set()); + EXPECT_FALSE(long_only_opt->is_set()); +} + +TEST(args, options_not_set_arguments) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("bar").add("fum").build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + ASSERT_EQ(2, out.size()); + EXPECT_EQ("bar", out[0]); + EXPECT_EQ("fum", out[1]); +} + +TEST(args, options_set) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo") + .add("-a") + .add("-b") + .add("--cold") + .add("-d") + .add("10") + .add("-e") + .add("hamburger") + .add("--form=circle") + .add("-g") + .add("-h") + .add("--invert") + .build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + EXPECT_TRUE(out.empty()); + EXPECT_TRUE(short_only->is_set()); + EXPECT_TRUE(short_long->is_set()); + EXPECT_TRUE(long_only->is_set()); + EXPECT_TRUE(short_only_req->is_set()); + EXPECT_TRUE(short_only_req->has_argument()); + EXPECT_EQ("10", short_only_req->argument()); + EXPECT_TRUE(short_long_req->is_set()); + EXPECT_TRUE(short_long_req->has_argument()); + EXPECT_EQ("hamburger", short_long_req->argument()); + EXPECT_TRUE(long_only_req->is_set()); + EXPECT_TRUE(long_only_req->has_argument()); + EXPECT_EQ("circle", long_only_req->argument()); + EXPECT_TRUE(short_only_opt->is_set()); + EXPECT_FALSE(short_only_opt->has_argument()); + EXPECT_TRUE(short_long_opt->is_set()); + EXPECT_FALSE(short_long_opt->has_argument()); + EXPECT_TRUE(long_only_opt->is_set()); + EXPECT_FALSE(long_only_opt->has_argument()); +} + +TEST(args, options_set_variant) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo") + .add("-a") + .add("--bold") + .add("--cold") + .add("-d") + .add("10") + .add("--eat=hamburger") + .add("--form") + .add("circle") + .add("-g") + .add("--hold=foo") + .add("--invert=bar") + .build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + EXPECT_TRUE(out.empty()); + EXPECT_TRUE(short_only->is_set()); + EXPECT_TRUE(short_long->is_set()); + EXPECT_TRUE(long_only->is_set()); + EXPECT_TRUE(short_only_req->is_set()); + EXPECT_TRUE(short_only_req->has_argument()); + EXPECT_EQ("10", short_only_req->argument()); + EXPECT_TRUE(short_long_req->is_set()); + EXPECT_TRUE(short_long_req->has_argument()); + EXPECT_EQ("hamburger", short_long_req->argument()); + EXPECT_TRUE(long_only_req->is_set()); + EXPECT_TRUE(long_only_req->has_argument()); + EXPECT_EQ("circle", long_only_req->argument()); + EXPECT_TRUE(short_only_opt->is_set()); + EXPECT_FALSE(short_only_opt->has_argument()); + EXPECT_TRUE(short_long_opt->is_set()); + EXPECT_TRUE(short_long_opt->has_argument()); + EXPECT_EQ("foo", short_long_opt->argument()); + EXPECT_TRUE(long_only_opt->is_set()); + EXPECT_TRUE(long_only_opt->has_argument()); + EXPECT_EQ("bar", long_only_opt->argument()); +} + +TEST(args, options_short_missing_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("-d").build(); + EXPECT_FALSE(args->run(arg.c(), arg.v())); + std::stringstream ss; + args->print_error(ss); + EXPECT_EQ("foo: option requires an argument -- 'd'\n", ss.str()); +} + +TEST(args, options_short_unknown_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("-X").build(); + EXPECT_FALSE(args->run(arg.c(), arg.v())); + std::stringstream ss; + args->print_error(ss); + EXPECT_EQ("foo: invalid option -- 'X'\n", ss.str()); +} + +TEST(args, options_long_missing_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("--form").build(); + EXPECT_FALSE(args->run(arg.c(), arg.v())); + std::stringstream ss; + args->print_error(ss); + EXPECT_EQ("foo: option '--form' requires an argument\n", ss.str()); +} + +TEST(args, options_long_unsupported_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("--cold=feet").build(); + EXPECT_FALSE(args->run(arg.c(), arg.v())); + std::stringstream ss; + args->print_error(ss); + EXPECT_EQ("foo: option '--cold' doesn't allow an argument\n", ss.str()); +} + +TEST(args, options_long_unknown_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("--experience").build(); + EXPECT_FALSE(args->run(arg.c(), arg.v())); + std::stringstream ss; + args->print_error(ss); + EXPECT_EQ("foo: unrecognized option '--experience'\n", ss.str()); +} + +TEST(args, options_short_dash_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("-d").add("-").build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + EXPECT_TRUE(out.empty()); + EXPECT_TRUE(short_only_req->is_set()); + EXPECT_TRUE(short_only_req->has_argument()); + EXPECT_EQ("-", short_only_req->argument()); +} + +TEST(args, options_long_dash_dash_value) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("--eat").add("--").build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + EXPECT_TRUE(out.empty()); + EXPECT_TRUE(short_long_req->is_set()); + EXPECT_TRUE(short_long_req->has_argument()); + EXPECT_EQ("--", short_long_req->argument()); +} + +TEST(args, options_dash_dash) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo") + .add("--") + .add("-d") + .add("10") + .add("--eat=hamburger") + .add("--form") + .add("circle") + .build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + ASSERT_EQ(5, out.size()); + EXPECT_EQ("-d", out[0]); + EXPECT_EQ("10", out[1]); + EXPECT_EQ("--eat=hamburger", out[2]); + EXPECT_EQ("--form", out[3]); + EXPECT_EQ("circle", out[4]); + EXPECT_FALSE(short_only_req->is_set()); + EXPECT_FALSE(short_long_req->is_set()); + EXPECT_FALSE(long_only_req->is_set()); +} + +TEST(args, options_dash_dash_end) { + auto args = Args::create(); + SETUP_OPTIONS(args); + Builder builder; + auto arg = builder.add("foo").add("--").build(); + std::vector out; + EXPECT_TRUE(args->run(arg.c(), arg.v(), &out)); + EXPECT_TRUE(out.empty()); +} + +TEST(args, help) { + auto args = Args::create(); + SETUP_OPTIONS(args); + std::stringstream ss; + args->print_help(ss, 50); + EXPECT_EQ(R"(Mandatory arguments to long options are mandatory + for short options too. + -a an option + -b, --bold set font style to bold + --cold use if it is cold outside + -d ARG distance + -e, --eat=FOOD what to order, what to eat? + --form=ARG circle, shape or something + else? + -g + -h, --hold=[ARG] + --invert=[ARG] +)", + ss.str()); +} + +TEST(args, help_wide) { + auto args = Args::create(); + SETUP_OPTIONS(args); + std::stringstream ss; + args->print_help(ss, 100); + EXPECT_EQ( + R"(Mandatory arguments to long options are mandatory for short options too. + -a an option + -b, --bold set font style to bold + --cold use if it is cold outside + -d ARG distance + -e, --eat=FOOD what to order, what to eat? + --form=ARG circle, shape or something else? + -g + -h, --hold=[ARG] + --invert=[ARG] +)", + ss.str()); +} + +TEST(args, help_narrow) { + auto args = Args::create(); + SETUP_OPTIONS(args); + std::stringstream ss; + args->print_help(ss, 35); + EXPECT_EQ(R"(Mandatory arguments to long options + are mandatory for short options + too. + -a an option + -b, --bold set font style to + bold + --cold use if it is cold + outside + -d ARG distance + -e, --eat=FOOD +what to order, what to eat? + --form=ARG +circle, shape or something else? + -g + -h, --hold=[ARG] + --invert=[ARG] +)", + ss.str()); +} + +TEST(args, help_very_narrow) { + auto args = Args::create(); + SETUP_OPTIONS(args); + std::stringstream ss; + args->print_help(ss, 20); + EXPECT_EQ(R"(Mandatory arguments + to long options are + mandatory for short + options too. + -a an option + -b, --bold +set font style to + bold + --cold +use if it is cold + outside + -d ARG distance + -e, --eat=FOOD +what to order, what + to eat? + --form=ARG +circle, shape or + something else? + -g + -h, --hold=[ARG] + --invert=[ARG] +)", + ss.str()); +} + +TEST(args, help_long_word) { + auto args = Args::create(); + std::stringstream ss; + auto opt = args->option('a', "arg", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + args->print_help(ss, 20); + EXPECT_EQ(R"(Mandatory arguments + to long options are + mandatory for short + options too. + -a, --arg +aaaaaaaaaaaaaaaaaaa- +aaaaaaaaaaaaaaaaaaa +)", + ss.str()); +} diff --git a/test/buffer.cc b/test/buffer.cc new file mode 100644 index 0000000..897bd88 --- /dev/null +++ b/test/buffer.cc @@ -0,0 +1,270 @@ +#include "buffer.hh" + +#include +#include +#include +#include +#include + +namespace { + +enum class BufferType : uint8_t { + Fixed, + Dynamic, +}; + +class BufferTest : public testing::TestWithParam { + protected: + static std::unique_ptr make(size_t min_size, size_t max_size) { + switch (GetParam()) { + case BufferType::Fixed: + return Buffer::fixed(min_size); + case BufferType::Dynamic: + return Buffer::dynamic(min_size, max_size); + } + std::unreachable(); + } +}; + +} // namespace + +TEST_P(BufferTest, empty) { + auto buffer = make(10, 100); + EXPECT_TRUE(buffer->empty()); + EXPECT_FALSE(buffer->full()); + size_t avail; + buffer->rptr(avail); + EXPECT_EQ(0, avail); + buffer->wptr(avail); + EXPECT_EQ(10, avail); +} + +TEST_P(BufferTest, write_read) { + auto buffer = make(10, 100); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "Hello", 6); + buffer->commit(6); + EXPECT_FALSE(buffer->empty()); + auto* rptr = buffer->rptr(avail); + EXPECT_EQ(6, avail); + EXPECT_STREQ("Hello", reinterpret_cast(rptr)); + buffer->consume(3); + rptr = buffer->rptr(avail); + EXPECT_EQ(3, avail); + EXPECT_STREQ("lo", reinterpret_cast(rptr)); + buffer->consume(3); + EXPECT_TRUE(buffer->empty()); +} + +TEST_P(BufferTest, write_read2) { + auto buffer = make(10, 10); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "0123456789", 10); + buffer->commit(10); + + auto* rptr = buffer->rptr(avail); + EXPECT_EQ(10, avail); + char tmp[11]; + memcpy(tmp, rptr, 5); + tmp[5] = '\0'; + EXPECT_STREQ("01234", tmp); + buffer->consume(5); + + wptr = buffer->wptr(avail, 5); + EXPECT_EQ(5, avail); + memcpy(wptr, "abcde", 5); + buffer->commit(5); + + rptr = buffer->rptr(avail); + EXPECT_LE(5, avail); + memcpy(tmp, rptr, 5); + tmp[5] = '\0'; + EXPECT_STREQ("56789", tmp); + buffer->consume(5); + + rptr = buffer->rptr(avail, 5); + EXPECT_EQ(5, avail); + memcpy(tmp, rptr, 5); + tmp[5] = '\0'; + EXPECT_STREQ("abcde", tmp); + buffer->consume(5); +} + +TEST_P(BufferTest, write_read3) { + auto buffer = make(10, 10); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "0123456789", 10); + buffer->commit(10); + + auto* rptr = buffer->rptr(avail); + EXPECT_EQ(10, avail); + char tmp[11]; + memcpy(tmp, rptr, 5); + tmp[5] = '\0'; + EXPECT_STREQ("01234", tmp); + buffer->consume(5); + + wptr = buffer->wptr(avail, 5); + EXPECT_EQ(5, avail); + memcpy(wptr, "abcde", 5); + buffer->commit(5); + + rptr = buffer->rptr(avail, 10); + EXPECT_EQ(10, avail); + memcpy(tmp, rptr, 10); + tmp[10] = '\0'; + EXPECT_STREQ("56789abcde", tmp); + buffer->consume(5); +} + +TEST_P(BufferTest, write_read4) { + auto buffer = make(10, 10); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "0123456789", 10); + buffer->commit(10); + + auto* rptr = buffer->rptr(avail); + EXPECT_EQ(10, avail); + char tmp[11]; + memcpy(tmp, rptr, 10); + tmp[10] = '\0'; + EXPECT_STREQ("0123456789", tmp); + buffer->consume(10); +} + +TEST_P(BufferTest, write_read5) { + auto buffer = make(10, 10); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "01234", 5); + buffer->commit(5); + + auto* rptr = buffer->rptr(avail); + EXPECT_EQ(5, avail); + char tmp[11]; + memcpy(tmp, rptr, 3); + tmp[3] = '\0'; + EXPECT_STREQ("012", tmp); + buffer->consume(3); + + wptr = buffer->wptr(avail, 8); + EXPECT_EQ(8, avail); + memcpy(wptr, "", 8); + buffer->commit(8); + + rptr = buffer->rptr(avail, 10); + EXPECT_EQ(10, avail); + memcpy(tmp, rptr, 10); + tmp[10] = '\0'; + EXPECT_STREQ("34", tmp); + buffer->consume(10); +} + +TEST_P(BufferTest, write_read6) { + auto buffer = make(10, 10); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "0123456789", 10); + buffer->commit(10); + + auto* rptr = buffer->rptr(avail); + EXPECT_EQ(10, avail); + char tmp[11]; + memcpy(tmp, rptr, 8); + tmp[8] = '\0'; + EXPECT_STREQ("01234567", tmp); + buffer->consume(8); + + wptr = buffer->wptr(avail, 3); + EXPECT_LE(3, avail); + memcpy(wptr, "abc", 3); + buffer->commit(3); + + rptr = buffer->rptr(avail, 5); + EXPECT_EQ(5, avail); + memcpy(tmp, rptr, 5); + tmp[5] = '\0'; + EXPECT_STREQ("89abc", tmp); + buffer->consume(5); +} + +TEST_P(BufferTest, full) { + auto buffer = make(10, 10); + size_t avail; + auto* wptr = buffer->wptr(avail); + EXPECT_EQ(10, avail); + memcpy(wptr, "0123456789", 10); + buffer->commit(10); + EXPECT_TRUE(buffer->full()); + std::ignore = buffer->wptr(avail); + EXPECT_EQ(0, avail); + buffer->commit(0); + + auto* rptr = buffer->rptr(avail, 10); + EXPECT_EQ(10, avail); + char tmp[11]; + memcpy(tmp, rptr, 5); + tmp[5] = '\0'; + EXPECT_STREQ("01234", tmp); + buffer->consume(5); + EXPECT_FALSE(buffer->full()); + + wptr = buffer->wptr(avail, 5); + EXPECT_EQ(5, avail); + memcpy(wptr, "abcde", 5); + buffer->commit(5); + EXPECT_TRUE(buffer->full()); + + rptr = buffer->rptr(avail, 10); + EXPECT_EQ(10, avail); + memcpy(tmp, rptr, 10); + tmp[10] = '\0'; + EXPECT_STREQ("56789abcde", tmp); + buffer->consume(10); + EXPECT_FALSE(buffer->full()); + EXPECT_TRUE(buffer->empty()); + + std::ignore = buffer->rptr(avail, 10); + EXPECT_EQ(0, avail); + buffer->consume(0); +} + +INSTANTIATE_TEST_SUITE_P(AllTypes, BufferTest, + testing::Values(BufferType::Fixed, + BufferType::Dynamic)); + +TEST(buffer, dynamic_increase) { + auto buffer = Buffer::dynamic(10, 20); + + size_t avail; + auto* wptr = buffer->wptr(avail, 15); + EXPECT_EQ(15, avail); + memcpy(wptr, "0123456789abcde", 15); + buffer->commit(15); + EXPECT_FALSE(buffer->full()); + wptr = buffer->wptr(avail, 5); + EXPECT_EQ(5, avail); + memcpy(wptr, "fghij", 5); + buffer->commit(5); + EXPECT_TRUE(buffer->full()); + + auto* rptr = buffer->rptr(avail, 20); + EXPECT_EQ(20, avail); + char tmp[21]; + memcpy(tmp, rptr, 20); + tmp[20] = '\0'; + EXPECT_STREQ("0123456789abcdefghij", tmp); + buffer->consume(20); + EXPECT_FALSE(buffer->full()); + EXPECT_TRUE(buffer->empty()); +} diff --git a/test/io.cc b/test/io.cc new file mode 100644 index 0000000..04d97d6 --- /dev/null +++ b/test/io.cc @@ -0,0 +1,203 @@ +#include "io.hh" + +#include "io_test_helper.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +bool remove_recursive(int fd) { + auto* dir = fdopendir(fd); + if (!dir) + return false; + while (auto* ent = readdir(dir)) { + if (ent->d_name[0] == '.') { + if (ent->d_name[1] == '\0') + continue; + if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') + continue; + } + bool is_dir; + if (ent->d_type == DT_DIR) { + is_dir = true; + } else if (ent->d_type == DT_UNKNOWN) { + struct stat buf; + if (fstatat(dirfd(dir), ent->d_name, &buf, AT_SYMLINK_NOFOLLOW) == 0) { + is_dir = S_ISDIR(buf.st_mode); + } else { + if (errno != ENOENT) { + closedir(dir); + return false; + } + is_dir = false; + } + } else { + is_dir = false; + } + + if (is_dir) { + int fd2 = openat(dirfd(dir), ent->d_name, O_RDONLY | O_DIRECTORY); + if (fd2 == -1) { + if (errno != ENOENT) { + closedir(dir); + return false; + } + } else { + if (!remove_recursive(fd2)) { + closedir(dir); + return false; + } + } + } + if (unlinkat(dirfd(dir), ent->d_name, is_dir ? AT_REMOVEDIR : 0)) { + if (errno != ENOENT) { + closedir(dir); + return false; + } + } + } + closedir(dir); + return true; +} + +class IoTest : public testing::Test { + protected: + void SetUp() override { + // NOLINTNEXTLINE(misc-include-cleaner) + tmpdir_ = P_tmpdir "/jkc-test-io-XXXXXX"; + // NOLINTNEXTLINE(misc-include-cleaner) + auto* ret = mkdtemp(tmpdir_.data()); + ASSERT_EQ(ret, tmpdir_.data()); + dirfd_ = open(tmpdir_.c_str(), O_PATH | O_DIRECTORY); + ASSERT_NE(-1, dirfd_); + } + + void TearDown() override { + int fd = openat(dirfd_, ".", O_RDONLY | O_DIRECTORY); + EXPECT_NE(-1, fd); + if (fd != -1) { + EXPECT_TRUE(remove_recursive(fd)); + } + close(dirfd_); + rmdir(tmpdir_.c_str()); + } + + [[nodiscard]] int dirfd() const { return dirfd_; } + + void touch(const std::string& name, const std::string& value = "") { + auto fd = openat(dirfd(), name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0700); + EXPECT_NE(-1, fd); + if (fd == -1) + return; + size_t offset = 0; + while (offset < value.size()) { + auto ret = write(fd, value.data() + offset, value.size() - offset); + EXPECT_LT(0, ret); + if (ret <= 0) { + break; + } + offset += ret; + } + close(fd); + } + + private: + int dirfd_{-1}; + std::string tmpdir_; +}; + +} // namespace + +TEST_F(IoTest, no_such_file) { + auto ret = io::openat(dirfd(), "no-such-file"); + ASSERT_FALSE(ret.has_value()); + EXPECT_EQ(io::OpenError::NoSuchFile, ret.error()); +} + +TEST_F(IoTest, read_empty) { + touch("test"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + std::string tmp(10, ' '); + auto ret2 = ret.value()->read(tmp.data(), tmp.size()); + ASSERT_FALSE(ret2.has_value()); + EXPECT_EQ(io::ReadError::Eof, ret2.error()); +} + +TEST_F(IoTest, skip_empty) { + touch("test"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = ret.value()->skip(10); + ASSERT_FALSE(ret2.has_value()); + EXPECT_EQ(io::ReadError::Eof, ret2.error()); +} + +TEST_F(IoTest, read) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + std::string tmp(12, ' '); + auto ret2 = ret.value()->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(11, ret2.value()); + tmp.resize(ret2.value()); + EXPECT_EQ("hello world", tmp); +} + +TEST_F(IoTest, skip) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = ret.value()->repeat_skip(6); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(6, ret2.value()); + std::string tmp(12, ' '); + auto ret3 = ret.value()->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(5, ret3.value()); + tmp.resize(ret3.value()); + EXPECT_EQ("world", tmp); +} + +TEST_F(IoTest, read_block) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = io_make_max_block(std::move(ret.value()), 2); + std::string tmp(12, ' '); + auto ret3 = ret2->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(11, ret3.value()); + tmp.resize(ret3.value()); + EXPECT_EQ("hello world", tmp); +} + +TEST_F(IoTest, skip_block) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = io_make_max_block(std::move(ret.value()), 2); + auto ret3 = ret2->repeat_skip(6); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(6, ret3.value()); + std::string tmp(12, ' '); + auto ret4 = ret2->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret4.has_value()); + EXPECT_EQ(5, ret4.value()); + tmp.resize(ret4.value()); + EXPECT_EQ("world", tmp); +} diff --git a/test/io_test_helper.cc b/test/io_test_helper.cc new file mode 100644 index 0000000..9ac663a --- /dev/null +++ b/test/io_test_helper.cc @@ -0,0 +1,82 @@ +#include "io_test_helper.hh" + +#include "io.hh" + +#include +#include +#include +#include +#include + +namespace { + +class BreakingReader : public io::Reader { + public: + BreakingReader(std::unique_ptr reader, size_t offset, + io::ReadError error) + : reader_(std::move(reader)), offset_(offset), error_(error) {} + + [[nodiscard]] + std::expected read(void* dst, size_t max) override { + if (offset_ == 0) + return std::unexpected(error_); + size_t avail = std::min(offset_, max); + auto ret = reader_->read(dst, avail); + if (ret.has_value()) { + offset_ -= ret.value(); + } + return ret; + } + + [[nodiscard]] + std::expected skip(size_t max) override { + if (offset_ == 0) + return std::unexpected(error_); + size_t avail = std::min(offset_, max); + auto ret = reader_->skip(avail); + if (ret.has_value()) { + offset_ -= ret.value(); + } + return ret; + } + + private: + std::unique_ptr reader_; + size_t offset_; + io::ReadError const error_; +}; + +class MaxBlockReader : public io::Reader { + public: + MaxBlockReader(std::unique_ptr reader, size_t max_block_size) + : reader_(std::move(reader)), max_block_size_(max_block_size) {} + + [[nodiscard]] + std::expected read(void* dst, size_t max) override { + size_t avail = std::min(max_block_size_, max); + return reader_->read(dst, avail); + } + + [[nodiscard]] + std::expected skip(size_t max) override { + size_t avail = std::min(max_block_size_, max); + return reader_->skip(avail); + } + + private: + std::unique_ptr reader_; + size_t const max_block_size_; +}; + +} // namespace + +std::unique_ptr io_make_breaking(std::unique_ptr reader, + size_t offset, + io::ReadError error) { + return std::make_unique(std::move(reader), offset, error); +} + +std::unique_ptr io_make_max_block( + std::unique_ptr reader, size_t max_block_size) { + return std::make_unique(std::move(reader), max_block_size); +} diff --git a/test/io_test_helper.hh b/test/io_test_helper.hh new file mode 100644 index 0000000..b99b8fa --- /dev/null +++ b/test/io_test_helper.hh @@ -0,0 +1,18 @@ +#ifndef IO_TEST_HELPER_HH +#define IO_TEST_HELPER_HH + +#include "io.hh" // IWYU pragma: export + +#include +#include + +[[nodiscard]] +std::unique_ptr io_make_breaking( + std::unique_ptr reader, size_t offset = 0, + io::ReadError error = io::ReadError::Error); + +[[nodiscard]] +std::unique_ptr io_make_max_block( + std::unique_ptr reader, size_t max_block_size); + +#endif // IO_TEST_HELPER_HH diff --git a/test/line.cc b/test/line.cc new file mode 100644 index 0000000..e0ea002 --- /dev/null +++ b/test/line.cc @@ -0,0 +1,182 @@ +#include "line.hh" + +#include "io_test_helper.hh" + +#include +#include +#include +#include + +TEST(line, empty) { + auto reader = line::open(io::memory("")); + EXPECT_EQ(0, reader->number()); + auto line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); + EXPECT_EQ(0, reader->number()); +} + +TEST(line, one_line) { + auto reader = line::open(io::memory("foo")); + EXPECT_EQ(0, reader->number()); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo", line.value()); + EXPECT_EQ(1, reader->number()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); + EXPECT_EQ(1, reader->number()); +} + +TEST(line, many_lines) { + auto reader = line::open(io::memory("foo\nbar\nfoobar\n")); + EXPECT_EQ(0, reader->number()); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo", line.value()); + EXPECT_EQ(1, reader->number()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("bar", line.value()); + EXPECT_EQ(2, reader->number()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foobar", line.value()); + EXPECT_EQ(3, reader->number()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); + EXPECT_EQ(3, reader->number()); +} + +TEST(line, many_lines_mixed) { + auto reader = line::open(io::memory("foo\r\nbar\rfoobar\n")); + EXPECT_EQ(0, reader->number()); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo", line.value()); + EXPECT_EQ(1, reader->number()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("bar", line.value()); + EXPECT_EQ(2, reader->number()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foobar", line.value()); + EXPECT_EQ(3, reader->number()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); + EXPECT_EQ(3, reader->number()); +} + +TEST(line, empty_line) { + auto reader = line::open(io::memory("\n")); + EXPECT_EQ(0, reader->number()); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("", line.value()); + EXPECT_EQ(1, reader->number()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); + EXPECT_EQ(1, reader->number()); +} + +TEST(line, max_line) { + auto reader = line::open(io::memory("012345678901234567890123456789"), 10); + EXPECT_EQ(0, reader->number()); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("0123456789", line.value()); + EXPECT_EQ(1, reader->number()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("0123456789", line.value()); + EXPECT_EQ(2, reader->number()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("0123456789", line.value()); + EXPECT_EQ(3, reader->number()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); + EXPECT_EQ(3, reader->number()); +} + +TEST(line, read_error) { + auto reader = line::open( + io_make_breaking(io::memory("foo bar fum\nfim zam"), /* offset */ 5)); + auto line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Error, line.error()); +} + +TEST(line, read_error_newline) { + auto reader = line::open( + io_make_breaking(io::memory("foo bar\r\nfim zam"), /* offset */ 8)); + auto line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Error, line.error()); +} + +TEST(line, blocky) { + auto reader = line::open(io_make_max_block(io::memory("foo bar\r\nfim zam"), + /* max_block_size */ 1)); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo bar", line.value()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("fim zam", line.value()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); +} + +TEST(line, blocky_newline) { + auto reader = line::open(io_make_max_block(io::memory("foo bar\r\nfim zam"), + /* max_block_size */ 8)); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo bar", line.value()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("fim zam", line.value()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); +} + +TEST(line, eof_newline) { + auto reader = line::open(io::memory("foo bar\r")); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo bar", line.value()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); +} + +TEST(line, max_newline) { + auto reader = line::open(io::memory("foo bar\r"), 6); + auto line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("foo ba", line.value()); + line = reader->read(); + ASSERT_TRUE(line.has_value()); + EXPECT_EQ("r", line.value()); + line = reader->read(); + ASSERT_FALSE(line.has_value()); + EXPECT_EQ(io::ReadError::Eof, line.error()); +} + +TEST(line, max_line_overflow) { + EXPECT_DEATH_IF_SUPPORTED( + { + std::ignore = + line::open(io::memory(""), std::numeric_limits::max()); + }, + ""); +} diff --git a/test/str.cc b/test/str.cc new file mode 100644 index 0000000..6d7edf2 --- /dev/null +++ b/test/str.cc @@ -0,0 +1,67 @@ +#include "str.hh" + +#include + +TEST(str, split) { + auto ret = str::split(""); + EXPECT_EQ(0, ret.size()); + + ret = str::split("", ' ', true); + ASSERT_EQ(1, ret.size()); + EXPECT_EQ("", ret[0]); + + ret = str::split(" "); + EXPECT_EQ(0, ret.size()); + + ret = str::split(" ", ' ', true); + ASSERT_EQ(2, ret.size()); + EXPECT_EQ("", ret[0]); + EXPECT_EQ("", ret[1]); + + ret = str::split(" a b "); + ASSERT_EQ(2, ret.size()); + EXPECT_EQ("a", ret[0]); + EXPECT_EQ("b", ret[1]); + + ret = str::split(" a b ", ' ', true); + ASSERT_EQ(4, ret.size()); + EXPECT_EQ("", ret[0]); + EXPECT_EQ("a", ret[1]); + EXPECT_EQ("b", ret[2]); + EXPECT_EQ("", ret[3]); + + ret = str::split(" a b", ' ', true); + ASSERT_EQ(3, ret.size()); + EXPECT_EQ("", ret[0]); + EXPECT_EQ("a", ret[1]); + EXPECT_EQ("b", ret[2]); +} + +TEST(str, trim) { + auto ret = str::trim(""); + EXPECT_EQ("", ret); + + ret = str::trim(" "); + EXPECT_EQ("", ret); + + ret = str::trim(" "); + EXPECT_EQ("", ret); + + ret = str::trim("foo"); + EXPECT_EQ("foo", ret); + + ret = str::trim(" foo"); + EXPECT_EQ("foo", ret); + + ret = str::trim(" foo "); + EXPECT_EQ("foo", ret); + + ret = str::trim("foo "); + EXPECT_EQ("foo", ret); + + ret = str::trim(" foo bar "); + EXPECT_EQ("foo bar", ret); + + ret = str::trim("\tfoo bar\r\n"); + EXPECT_EQ("foo bar", ret); +} -- cgit v1.2.3-70-g09d2