From 08a5f3441675c446482b473777cbba53d5191f20 Mon Sep 17 00:00:00 2001 From: Thomas Schmucker Date: Sun, 21 Jun 2026 17:20:13 +0200 Subject: rework: add testing framework --- tests/compile_flags.txt | 4 +- tests/config.mk | 5 +- tests/makefile | 20 +- tests/src/csv-test.c | 2 + tests/src/tester.cpp | 476 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 492 insertions(+), 15 deletions(-) create mode 100644 tests/src/tester.cpp (limited to 'tests') diff --git a/tests/compile_flags.txt b/tests/compile_flags.txt index 9352ecc..f77f48c 100644 --- a/tests/compile_flags.txt +++ b/tests/compile_flags.txt @@ -3,6 +3,6 @@ -Wsign-compare -Wsign-conversion -pedantic --std=c99 +-std=c++20 -O2 --I../lib/include +-I../libcsv/include diff --git a/tests/config.mk b/tests/config.mk index 4b51d37..72527d1 100644 --- a/tests/config.mk +++ b/tests/config.mk @@ -1,3 +1,2 @@ -BUILD_DIR=../build -CFLAGS=-Wall -Wextra -Wsign-compare -Wsign-conversion -pedantic -std=c99 -O2 -I../lib/include -LDFLAGS=-L$(BUILD_DIR) -lcsv +CXXFLAGS=-Wall -Wextra -Wsign-compare -Wsign-conversion -pedantic -std=c++20 -O2 -I../libcsv/include +LDFLAGS=-L../libcsv/lib -lcsv -lgtest -lgtest_main diff --git a/tests/makefile b/tests/makefile index 2300a19..979dbd2 100644 --- a/tests/makefile +++ b/tests/makefile @@ -1,24 +1,24 @@ include config.mk -TEST_RUNNER=$(BUILD_DIR)/tester -OBJS=$(BUILD_DIR)/csv-test.o +SRCS=$(wildcard src/*.cpp) +OBJS=$(patsubst src/%.cpp,obj/%.o,$(SRCS)) -$(TEST_RUNNER): $(OBJS) | libcsv - cc -o $@ $(LDFLAGS) $(OBJS) +bin/tester: $(OBJS) | bin libcsv + c++ -o $@ $(LDFLAGS) $(OBJS) -$(BUILD_DIR)/%.o: src/%.c | $(BUILD_DIR) - cc $(CFLAGS) -c $< -o $@ +obj/%.o: src/%.cpp | obj + c++ $(CXXFLAGS) -c $< -o $@ -$(BUILD_DIR): +bin obj: mkdir -p $@ .PHONY: libcsv libcsv: - $(MAKE) -C ../lib + $(MAKE) -C ../libcsv compile_flags.txt: - echo "$(CFLAGS)" | tr ' ' '\n' > $@ + echo "$(CXXFLAGS)" | tr ' ' '\n' > $@ .PHONY: clean clean: - rm -f $(TEST_RUNNER) $(OBJS) + rm -rf bin obj diff --git a/tests/src/csv-test.c b/tests/src/csv-test.c index c99c049..4035f70 100644 --- a/tests/src/csv-test.c +++ b/tests/src/csv-test.c @@ -1,3 +1,4 @@ +// NOLINTBEGIN #undef NDEBUG #include @@ -625,3 +626,4 @@ main(void) return EXIT_SUCCESS; } +// NOLINTEND diff --git a/tests/src/tester.cpp b/tests/src/tester.cpp new file mode 100644 index 0000000..75f1366 --- /dev/null +++ b/tests/src/tester.cpp @@ -0,0 +1,476 @@ +#include +#include +#include + +#include "csv.h" + +using namespace std; +using namespace testing; + +TEST(TestEmptyObject, StandardInitialization) +{ + csv_t csv{}; + ASSERT_THAT(csv_nfields(&csv), Eq(0)); +} + +TEST(TestEmptyObject, ExplicitInitialization) +{ + csv_t csv; + csv_init(&csv); + + ASSERT_THAT(csv_nfields(&csv), Eq(0)); +} + +class TestLineEndingsBase : public Test { + using file_ptr = unique_ptr; + +public: + explicit TestLineEndingsBase(string_view data) + : data_{ data } + , file_{ fmemopen(data_.data(), data_.size(), "r"), &fclose } + { + csv_init(&csv_); + } + + TestLineEndingsBase(string_view data, csv_options_t &csv_options) + : data_{ data } + , file_{ fmemopen(data_.data(), data_.size(), "r"), &fclose } + { + csv_init_opt(&csv_, &csv_options); + } + + ~TestLineEndingsBase() override + { + csv_cleanup(&csv_); + } + + TestLineEndingsBase(const TestLineEndingsBase &) = delete; + TestLineEndingsBase(TestLineEndingsBase &&) = delete; + void operator=(const TestLineEndingsBase &) = delete; + void operator=(TestLineEndingsBase &&) = delete; + + csv_t csv_{}; + string data_; + file_ptr file_; +}; + +class TestLineEndings : public TestLineEndingsBase { +protected: + TestLineEndings() + : TestLineEndingsBase("A,B,C\r\nD,E,F\n") + { + } +}; + +TEST_F(TestLineEndings, Part1) +{ + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "D"); + EXPECT_STREQ(csv_field(&csv_, 1), "E"); + EXPECT_STREQ(csv_field(&csv_, 2), "F"); + + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(0)); +} + +class TestEmptyLinesWithDifferentLineEndings : public TestLineEndingsBase { +protected: + TestEmptyLinesWithDifferentLineEndings() + : TestLineEndingsBase("\r\n\n") + { + } +}; + +TEST_F(TestEmptyLinesWithDifferentLineEndings, Part1) +{ + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(0)); +} + +class TestEmptyFieldsWithDifferentLineEndings : public TestLineEndingsBase { + // NOLINTBEGIN + constexpr static const char *data = "\n" // L1 + "\r" // L2 + "\r\n" // L3 + "\"\"\n" // L4 + "\"\"\r" // L5 + "\"\"\r\n" // L6 + ",\n" // L7 + ",\r" // L8 + ",\r\n" // L9 + "\"\",\"\"\n" // L10 + "\"\",\"\"\r" // L11 + "\"\",\"\"\r\n" // L12 + ",,\n" // L13 + ",,\r" // L14 + ",,\r\n" // L15 + "\"\",\"\",\n" // L16 + "\"\",\"\",\r" // L17 + "\"\",\"\",\r\n" // L18 + ",,"; // L19 + + // NOLINTEND +protected: + TestEmptyFieldsWithDifferentLineEndings() + : TestLineEndingsBase(data) + { + } +}; + +TEST_F(TestEmptyFieldsWithDifferentLineEndings, Part1) +{ + // Line 1 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + // Line 2 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + // Line 3 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + // Line 4 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + // Line 5 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + // Line 6 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + + // Line 7 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + + // Line 8 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + + // Line 9 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + + // Line 10 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + + // Line 11 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + + // Line 12 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + + // Line 13 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // Line 14 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // Line 15 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // Line 16 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // Line 17 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // Line 18 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // Line 19 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), ""); + EXPECT_STREQ(csv_field(&csv_, 1), ""); + EXPECT_STREQ(csv_field(&csv_, 2), ""); + + // EOF + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(0)); + EXPECT_THAT(csv_nfields(&csv_), Eq(0)); +} + +class TestFieldsWithDifferentLineEndings : public TestLineEndingsBase { + // NOLINTBEGIN + static constexpr const char *data = "A\n" // L1 + "A\r" // L2 + "A\r\n" // L3 + "\"A\"\n" // L4 + "\"A\"\r" // L5 + "\"A\"\r\n" // L6 + "A,B\n" // L7 + "A,B\r" // L8 + "A,B\r\n" // L9 + "\"A\",\"B\"\n" // L10 + "\"A\",\"B\"\r" // L11 + "\"A\",\"B\"\r\n" // L12 + "A,B,C\n" // L13 + "A,B,C\r" // L14 + "A,B,C\r\n" // L15 + "\"A\",\"B\",C\n" // L16 + "\"A\",\"B\",C\r" // L17 + "\"A\",\"B\",C\r\n" // L18 + "A,B,C"; // L19 + + // NOLINTEND +protected: + TestFieldsWithDifferentLineEndings() + : TestLineEndingsBase(data) + { + } +}; + +TEST_F(TestFieldsWithDifferentLineEndings, Part1) +{ + // Line 1 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + + // Line 2 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + + // Line 3 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + + // Line 4 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + + // Line 5 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + + // Line 6 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + + // Line 7 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + + // Line 8 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + + // Line 9 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + + // Line 10 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + + // Line 11 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + + // Line 12 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + + // Line 13 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // Line 14 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // Line 15 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // Line 16 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // Line 17 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // Line 18 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // Line 19 + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(3)); + EXPECT_THAT(csv_nfields(&csv_), Eq(3)); + EXPECT_STREQ(csv_field(&csv_, 0), "A"); + EXPECT_STREQ(csv_field(&csv_, 1), "B"); + EXPECT_STREQ(csv_field(&csv_, 2), "C"); + + // EOF + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(0)); + EXPECT_THAT(csv_nfields(&csv_), Eq(0)); +} + +class TestQuotedFields : public TestLineEndingsBase { + // NOLINTBEGIN + static constexpr const char *data = "foo \"baz\" bar,foo \"\"baz\"\" bar,\"foo \"\"baz\"\" bar\",\"foo \"baz\" bar\",\"foo \"\"baz\"\", bar\""; + // NOLINTEND + +protected: + TestQuotedFields() + : TestLineEndingsBase(data) + { + } +}; + +TEST_F(TestQuotedFields, Part1) +{ + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(5)); + EXPECT_THAT(csv_nfields(&csv_), Eq(5)); + + EXPECT_STREQ(csv_field(&csv_, 0), "foo \"baz\" bar"); + EXPECT_STREQ(csv_field(&csv_, 1), "foo \"\"baz\"\" bar"); + EXPECT_STREQ(csv_field(&csv_, 2), "foo \"baz\" bar"); + EXPECT_STREQ(csv_field(&csv_, 3), "foo \"baz\" bar"); + EXPECT_STREQ(csv_field(&csv_, 4), "foo \"baz\", bar"); + + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(0)); + EXPECT_THAT(csv_nfields(&csv_), Eq(0)); +} + +class TestWrongQuotedFields : public TestLineEndingsBase { +protected: + TestWrongQuotedFields() + : TestLineEndingsBase("\"foo") + { + } +}; + +TEST_F(TestWrongQuotedFields, Part1) +{ + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(1)); + EXPECT_THAT(csv_nfields(&csv_), Eq(1)); + + EXPECT_STREQ(csv_field(&csv_, 0), "foo"); + + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(0)); + EXPECT_THAT(csv_nfields(&csv_), Eq(0)); +} + +class TestOutOfRangeError : public TestLineEndingsBase { +protected: + TestOutOfRangeError() + : TestLineEndingsBase(",") + { + csv_options.cb_error = &cb_error; + csv_init_opt(&csv_, &csv_options); + } + + static void cb_error(csv_err_t /*unused*/, void * /*unused*/) + { + throw out_of_range("out of range"); + } + + csv_options_t csv_options{ csv_default_options }; +}; + +TEST_F(TestOutOfRangeError, Part1) +{ + EXPECT_THAT(csv_read(&csv_, file_.get()), Eq(2)); + EXPECT_THAT(csv_nfields(&csv_), Eq(2)); + + EXPECT_THROW({ csv_field(&csv_, 2); }, out_of_range); +} -- cgit v1.3