#include "test_parser1.hpp" TEST_CASE("test file not found") { unique_file_name f{"file_not_found"}; { ss::parser p{f.name, ","}; CHECK_FALSE(p.valid()); } { ss::parser p{f.name, ","}; CHECK_FALSE(p.valid()); CHECK_FALSE(p.error_msg().empty()); } try { ss::parser p{f.name, ","}; FAIL("Expected exception..."); } catch (const std::exception& e) { CHECK_FALSE(std::string{e.what()}.empty()); } } TEST_CASE("test null buffer") { { ss::parser p{nullptr, 10, ","}; CHECK_FALSE(p.valid()); } { ss::parser p{nullptr, 10, ","}; CHECK_FALSE(p.valid()); CHECK_FALSE(p.error_msg().empty()); } try { ss::parser p{nullptr, 10, ","}; FAIL("Expected exception..."); } catch (const std::exception& e) { CHECK_FALSE(std::string{e.what()}.empty()); } } struct Y { constexpr static auto delim = ","; std::string s1; std::string s2; std::string s3; std::string to_string() const { return std::string{} .append(s1) .append(delim) .append(s2) .append(delim) .append(s3); } [[nodiscard]] auto tied() const { return std::tie(s1, s2, s3); } }; TEST_CASE_TEMPLATE("test position method", T, ParserOptionCombinations) { constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; unique_file_name f{"position_method"}; std::vector data = {{"1", "21", "x"}, {"321", "4", "y"}, {"54", "6", "zz"}, {"7", "876", "uuuu"}, {"910", "10", "v"}, {"10", "321", "ww"}}; make_and_write(f.name, data); auto [p, buff] = make_parser(f.name); auto data_at = [&buff = buff, &f = f](auto n) { if (!buff.empty()) { return buff[n]; } else { auto file = std::fopen(f.name.c_str(), "r"); std::fseek(file, n, SEEK_SET); return static_cast(std::fgetc(file)); } }; while (!p.eof()) { auto curr_char = p.position(); const auto& [s1, s2, s3] = p.template get_next(); auto s = s1 + "," + s2 + "," + s3; for (size_t i = 0; i < s1.size(); ++i) { CHECK_EQ(data_at(curr_char + i), s[i]); } auto last_char = data_at(curr_char + s.size()); CHECK((last_char == '\n' || last_char == '\r')); } } TEST_CASE_TEMPLATE("test line method", T, ParserOptionCombinations) { constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; unique_file_name f{"line_method"}; std::vector data = {{"1", "21", "x"}, {"321", "4", "y"}, {"54", "6", "zz"}, {"7", "876", "uuuu"}, {"910", "10", "v"}, {"10", "321", "ww"}}; make_and_write(f.name, data); auto [p, buff] = make_parser(f.name); auto expected_line = 0; CHECK_EQ(p.line(), expected_line); while (!p.eof()) { std::ignore = p.template get_next(); ++expected_line; CHECK_EQ(p.line(), expected_line); } CHECK_EQ(p.line(), data.size()); } TEST_CASE_TEMPLATE("parser test various valid cases", T, ParserOptionCombinations) { constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; unique_file_name f{"various_valid_cases"}; std::vector data = {{1, 2, "x"}, {3, 4, "y"}, {5, 6, "z"}, {7, 8, "u"}, {9, 10, "v"}, {11, 12, "w"}}; make_and_write(f.name, data); auto csv_data_buffer = make_buffer(f.name); { auto [p, _] = make_parser(f.name, ","); ss::parser p0{std::move(p)}; p = std::move(p0); std::vector i; auto [p2, __] = make_parser(f.name, ","); std::vector i2; auto move_rotate = [&p = p, &p0 = p0] { auto p1 = std::move(p); p0 = std::move(p1); p = std::move(p0); }; while (!p.eof()) { move_rotate(); auto a = p.template get_next(); i.emplace_back(ss::to_object(a)); } for (const auto& a : p2.template iterate()) { i2.emplace_back(ss::to_object(a)); } CHECK_EQ(i, data); CHECK_EQ(i2, data); } { auto [p, _] = make_parser(f.name, ","); std::vector i; auto [p2, __] = make_parser(f.name, ","); std::vector i2; auto [p3, ___] = make_parser(f.name, ","); std::vector i3; std::vector expected = {std::begin(data) + 1, std::end(data)}; using tup = std::tuple; p.ignore_next(); while (!p.eof()) { auto a = p.template get_next(); i.emplace_back(ss::to_object(a)); } p2.ignore_next(); for (const auto& a : p2.template iterate()) { i2.emplace_back(ss::to_object(a)); } p3.ignore_next(); for (auto it = p3.template iterate().begin(); it != p3.template iterate().end(); ++it) { i3.emplace_back(ss::to_object(*it)); } CHECK_EQ(i, expected); CHECK_EQ(i2, expected); CHECK_EQ(i3, expected); } { auto [p, _] = make_parser(f.name, ","); std::vector i; auto [p2, __] = make_parser(f.name, ","); std::vector i2; while (!p.eof()) { i.push_back(p.template get_object()); } for (auto&& a : p2.template iterate_object()) { i2.push_back(std::move(a)); } CHECK_EQ(i, data); CHECK_EQ(i2, data); } { auto [p, _] = make_parser(f.name, ","); std::vector i; for (auto&& a : p.template iterate_object()) { i.push_back(std::move(a)); } CHECK_EQ(i, data); } { auto [p, _] = make_parser(f.name, ","); std::vector i; auto [p2, __] = make_parser(f.name, ","); std::vector i2; using tup = std::tuple; while (!p.eof()) { i.push_back(p.template get_object()); } for (auto it = p2.template iterate_object().begin(); it != p2.template iterate_object().end(); it++) { i2.push_back({it->i, it->d, it->s}); } CHECK_EQ(i, data); CHECK_EQ(i2, data); } { auto [p, _] = make_parser(f.name, ","); std::vector i; using tup = std::tuple; for (auto&& a : p.template iterate_object()) { i.push_back(std::move(a)); } CHECK_EQ(i, data); } { auto [p, _] = make_parser(f.name, ","); std::vector i; while (!p.eof()) { i.push_back(p.template get_next()); } CHECK_EQ(i, data); } { auto [p, _] = make_parser(f.name, ","); std::vector i; for (auto&& a : p.template iterate()) { i.push_back(std::move(a)); } CHECK_EQ(i, data); } { constexpr int excluded = 3; auto [p, _] = make_parser(f.name, ","); std::vector i; auto [p2, __] = make_parser(f.name, ","); std::vector i2; while (!p.eof()) { try { auto a = p.template get_object, double, std::string>(); if (p.valid()) { i.push_back(a); } } catch (...) { // ignore }; } if (!T::ThrowOnError) { for (auto&& a : p2.template iterate_object, double, std::string>()) { if (p2.valid()) { i2.push_back(std::move(a)); } } } std::vector expected; for (auto& x : data) { if (x.i != excluded) { expected.push_back(x); } } std::copy_if(data.begin(), data.end(), expected.begin(), [&](const X& x) { return x.i != excluded; }); CHECK_EQ(i, expected); if (!T::ThrowOnError) { CHECK_EQ(i2, expected); } } { auto [p, _] = make_parser(f.name, ","); std::vector i; auto [p2, __] = make_parser(f.name, ","); std::vector i2; while (!p.eof()) { try { auto a = p.template get_object, double, std::string>(); if (p.valid()) { i.push_back(a); } } catch (...) { // ignore } } if (!T::ThrowOnError) { for (auto&& a : p2.template iterate_object, double, std::string>()) { if (p2.valid()) { i2.push_back(std::move(a)); } } } std::vector expected = {{3, 4, "y"}}; CHECK_EQ(i, expected); if (!T::ThrowOnError) { CHECK_EQ(i2, expected); } } { unique_file_name empty_f{"various_valid_cases"}; std::vector empty_data = {}; make_and_write(empty_f.name, empty_data); auto [p, _] = make_parser(empty_f.name, ","); std::vector i; auto [p2, __] = make_parser(empty_f.name, ","); std::vector i2; while (!p.eof()) { i.push_back(p.template get_next()); } for (auto&& a : p2.template iterate()) { i2.push_back(std::move(a)); } CHECK(i.empty()); CHECK(i2.empty()); } } using test_tuple = std::tuple; struct test_struct { int i; double d; char c; auto tied() { return std::tie(i, d, c); } }; static inline void expect_test_struct(const test_struct&) { } TEST_CASE_TEMPLATE("parser test composite conversion", BufferMode, std::true_type, std::false_type) { constexpr auto buffer_mode = BufferMode::value; unique_file_name f{"composite_conversion"}; { std::ofstream out{f.name}; for (auto& i : {"10,a,11.1", "10,20,11.1", "junk", "10,11.1", "1,11.1,a", "junk", "10,junk", "11,junk", "10,11.1,c", "10,20", "10,22.2,f"}) { out << i << std::endl; } } auto [p, _] = make_parser(f.name, ","); auto fail = [] { FAIL(""); }; auto expect_error = [](auto error) { CHECK(!error.empty()); }; auto ignore_error = [] {}; REQUIRE(p.valid()); REQUIRE_FALSE(p.eof()); { constexpr static auto expectedData = std::tuple{10, 'a', 11.1}; auto [d1, d2, d3, d4] = p.template try_next(fail) .template or_else(fail) .template or_else( [&](auto&& data) { CHECK_EQ(data, expectedData); }) .on_error(fail) .template or_else(fail) .values(); REQUIRE(p.valid()); REQUIRE_FALSE(d1); REQUIRE_FALSE(d2); REQUIRE(d3); REQUIRE_FALSE(d4); CHECK_EQ(*d3, expectedData); } { REQUIRE(!p.eof()); constexpr static auto expectedData = std::tuple{10, 20, 11.1}; auto [d1, d2, d3, d4] = p.template try_next( [&](auto& i1, auto i2, double d) { CHECK_EQ(std::tie(i1, i2, d), expectedData); }) .on_error(fail) .template or_object(fail) .on_error(fail) .template or_else(fail) .on_error(fail) .template or_else(fail) .values(); REQUIRE(p.valid()); REQUIRE(d1); REQUIRE_FALSE(d2); REQUIRE_FALSE(d3); REQUIRE_FALSE(d4); CHECK_EQ(*d1, expectedData); } { REQUIRE(!p.eof()); auto [d1, d2, d3, d4, d5] = p.template try_object(fail) .on_error(expect_error) .template or_else(fail) .template or_else(fail) .template or_else(fail) .template or_else(fail) .values(); REQUIRE_FALSE(p.valid()); REQUIRE_FALSE(d1); REQUIRE_FALSE(d2); REQUIRE_FALSE(d3); REQUIRE_FALSE(d4); REQUIRE_FALSE(d5); } { REQUIRE(!p.eof()); auto [d1, d2] = p.template try_next([](auto& i, auto& d) { REQUIRE_EQ(std::tie(i, d), std::tuple{10, 11.1}); }) .template or_else([](auto&, auto&) { FAIL(""); }) .values(); REQUIRE(p.valid()); REQUIRE(d1); REQUIRE_FALSE(d2); } { REQUIRE(!p.eof()); auto [d1, d2] = p.template try_next([](auto&, auto&) { FAIL(""); }) .template or_else(expect_test_struct) .values(); REQUIRE(p.valid()); REQUIRE_FALSE(d1); REQUIRE(d2); CHECK_EQ(d2->tied(), std::tuple{1, 11.1, 'a'}); } { REQUIRE(!p.eof()); auto [d1, d2, d3, d4, d5] = p.template try_next(fail) .template or_object() .template or_else(expect_test_struct) .template or_else(fail) .template or_else>(fail) .on_error(ignore_error) .on_error(expect_error) .values(); REQUIRE_FALSE(p.valid()); REQUIRE_FALSE(d1); REQUIRE_FALSE(d2); REQUIRE_FALSE(d3); REQUIRE_FALSE(d4); REQUIRE_FALSE(d5); } { REQUIRE(!p.eof()); auto [d1, d2] = p.template try_next>() .on_error(ignore_error) .on_error(fail) .template or_else>(fail) .on_error(ignore_error) .on_error(fail) .on_error(ignore_error) .values(); REQUIRE(p.valid()); REQUIRE(d1); REQUIRE_FALSE(d2); CHECK_EQ(*d1, std::tuple{10, std::nullopt}); } { REQUIRE_FALSE(p.eof()); auto [d1, d2] = p.template try_next>() .on_error(fail) .template or_else>(fail) .on_error(fail) .values(); REQUIRE(p.valid()); REQUIRE(d1); REQUIRE_FALSE(d2); CHECK_EQ(*d1, std::tuple{11, std::variant{"junk"}}); } { REQUIRE(!p.eof()); auto [d1, d2] = p.template try_object() .template or_else(fail) .values(); REQUIRE(p.valid()); REQUIRE(d1); REQUIRE_FALSE(d2); CHECK_EQ(d1->tied(), std::tuple{10, 11.1, 'c'}); } { REQUIRE_FALSE(p.eof()); auto [d1, d2, d3, d4] = p.template try_next([] { return false; }) .template or_else([](auto&) { return false; }) .template or_else() .template or_else(fail) .values(); REQUIRE(p.valid()); REQUIRE_FALSE(d1); REQUIRE_FALSE(d2); REQUIRE(d3); REQUIRE_FALSE(d4); CHECK_EQ(d3.value(), std::tuple{10, 20}); } { REQUIRE(!p.eof()); auto [d1, d2, d3, d4] = p.template try_object( [] { return false; }) .template or_else([](auto&) { return false; }) .template or_object() .template or_else(fail) .values(); REQUIRE(p.valid()); REQUIRE_FALSE(d1); REQUIRE_FALSE(d2); REQUIRE(d3); REQUIRE_FALSE(d4); CHECK_EQ(d3->tied(), std::tuple{10, 22.2, 'f'}); } CHECK(p.eof()); } template void test_no_new_line_at_eof_impl(const std::vector& data) { unique_file_name f{"no_new_line_at_eof"}; make_and_write(f.name, data, {}, false); auto [p, _] = make_parser(f.name); std::vector parsed_data; for (const auto& el : p.template iterate()) { parsed_data.push_back(el); } CHECK_EQ(data, parsed_data); } template void test_no_new_line_at_eof() { test_no_new_line_at_eof_impl({}); test_no_new_line_at_eof_impl({{1, 2, "X"}}); test_no_new_line_at_eof_impl({{1, 2, "X"}, {}}); test_no_new_line_at_eof_impl( {{1, 2, "X"}, {3, 4, "YY"}}); test_no_new_line_at_eof_impl( {{1, 2, "X"}, {3, 4, "YY"}, {}}); test_no_new_line_at_eof_impl( {{1, 2, "X"}, {3, 4, "YY"}, {5, 6, "ZZZ"}, {7, 8, "UUU"}}); for (size_t i = 0; i < 2 * ss::get_line_initial_buffer_size; ++i) { test_no_new_line_at_eof_impl( {{1, 2, std::string(i, 'X')}}); for (size_t j = 0; j < 2 * ss::get_line_initial_buffer_size; j += 13) { test_no_new_line_at_eof_impl( {{1, 2, std::string(i, 'X')}, {3, 4, std::string(j, 'Y')}}); test_no_new_line_at_eof_impl( {{1, 2, std::string(j, 'X')}, {3, 4, std::string(i, 'Y')}}); } } } TEST_CASE_TEMPLATE("test no new line at end of data", T, ParserOptionCombinations) { constexpr auto buffer_mode = T::BufferMode::value; using ErrorMode = typename T::ErrorMode; test_no_new_line_at_eof(); }