mirror of
https://github.com/red0124/ssp.git
synced 2025-12-14 21:59:55 +01:00
Split parser tests into multiple files, add more tests for buffer mode
This commit is contained in:
309
test/test_parser1_2.cpp
Normal file
309
test/test_parser1_2.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
#include "test_parser1.hpp"
|
||||
|
||||
struct my_string {
|
||||
char* data{nullptr};
|
||||
|
||||
my_string() = default;
|
||||
|
||||
~my_string() {
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
// make sure no object is copied
|
||||
my_string(const my_string&) = delete;
|
||||
my_string& operator=(const my_string&) = delete;
|
||||
|
||||
my_string(my_string&& other) : data{other.data} {
|
||||
other.data = nullptr;
|
||||
}
|
||||
|
||||
my_string& operator=(my_string&& other) {
|
||||
data = other.data;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
inline bool ss::extract(const char* begin, const char* end, my_string& s) {
|
||||
size_t size = end - begin;
|
||||
s.data = new char[size + 1];
|
||||
strncpy(s.data, begin, size);
|
||||
s.data[size] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
struct xyz {
|
||||
my_string x;
|
||||
my_string y;
|
||||
my_string z;
|
||||
auto tied() {
|
||||
return std::tie(x, y, z);
|
||||
}
|
||||
};
|
||||
|
||||
template <bool buffer_mode, typename... Ts>
|
||||
void test_moving_of_parsed_composite_values() {
|
||||
// to compile is enough
|
||||
return;
|
||||
auto [p, _] = make_parser<buffer_mode, Ts...>("", "");
|
||||
p.template try_next<my_string, my_string, my_string>()
|
||||
.template or_else<my_string, my_string, my_string, my_string>(
|
||||
[](auto&&) {})
|
||||
.template or_else<my_string>([](auto&) {})
|
||||
.template or_else<xyz>([](auto&&) {})
|
||||
.template or_object<xyz, my_string, my_string, my_string>([](auto&&) {})
|
||||
.template or_else<std::tuple<my_string, my_string, my_string>>(
|
||||
[](auto&, auto&, auto&) {});
|
||||
}
|
||||
|
||||
TEST_CASE("parser test the moving of parsed composite values") {
|
||||
test_moving_of_parsed_composite_values<false>();
|
||||
test_moving_of_parsed_composite_values<false, ss::string_error>();
|
||||
test_moving_of_parsed_composite_values<true>();
|
||||
test_moving_of_parsed_composite_values<true, ss::string_error>();
|
||||
}
|
||||
|
||||
TEST_CASE("parser test error mode") {
|
||||
unique_file_name f{"test_parser"};
|
||||
{
|
||||
std::ofstream out{f.name};
|
||||
out << "junk" << std::endl;
|
||||
out << "junk" << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
auto [p, _] = make_parser<false, ss::string_error>(f.name, ",");
|
||||
|
||||
REQUIRE_FALSE(p.eof());
|
||||
p.get_next<int>();
|
||||
CHECK_FALSE(p.valid());
|
||||
CHECK_FALSE(p.error_msg().empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto [p, _] = make_parser<true, ss::string_error>(f.name, ",");
|
||||
|
||||
REQUIRE_FALSE(p.eof());
|
||||
p.get_next<int>();
|
||||
CHECK_FALSE(p.valid());
|
||||
CHECK_FALSE(p.error_msg().empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parser throw on error mode") {
|
||||
unique_file_name f{"test_parser"};
|
||||
{
|
||||
std::ofstream out{f.name};
|
||||
out << "junk" << std::endl;
|
||||
out << "junk" << std::endl;
|
||||
}
|
||||
|
||||
{
|
||||
auto [p, _] = make_parser<false, ss::throw_on_error>(f.name, ",");
|
||||
|
||||
REQUIRE_FALSE(p.eof());
|
||||
try {
|
||||
p.get_next<int>();
|
||||
FAIL("Expected exception...");
|
||||
} catch (const std::exception& e) {
|
||||
CHECK_FALSE(std::string{e.what()}.empty());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto [p, _] = make_parser<true, ss::throw_on_error>(f.name, ",");
|
||||
|
||||
REQUIRE_FALSE(p.eof());
|
||||
try {
|
||||
p.get_next<int>();
|
||||
FAIL("Expected exception...");
|
||||
} catch (const std::exception& e) {
|
||||
CHECK_FALSE(std::string{e.what()}.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline std::string no_quote(const std::string& s) {
|
||||
if (!s.empty() && s[0] == '"') {
|
||||
return {std::next(begin(s)), std::prev(end(s))};
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
template <bool buffer_mode, typename... Ts>
|
||||
void test_quote_multiline() {
|
||||
unique_file_name f{"test_parser"};
|
||||
std::vector<X> data = {{1, 2, "\"x\r\nx\nx\""},
|
||||
{3, 4, "\"y\ny\r\ny\""},
|
||||
{5, 6, "\"z\nz\""},
|
||||
{7, 8, "\"u\"\"\""},
|
||||
{9, 10, "v"},
|
||||
{11, 12, "\"w\n\""}};
|
||||
for (auto& [_, __, s] : data) {
|
||||
update_if_crlf(s);
|
||||
}
|
||||
|
||||
make_and_write(f.name, data);
|
||||
for (auto& [_, __, s] : data) {
|
||||
s = no_quote(s);
|
||||
if (s[0] == 'u') {
|
||||
s = "u\"";
|
||||
}
|
||||
}
|
||||
|
||||
auto [p, _] =
|
||||
make_parser<buffer_mode, ss::multiline, ss::quote<'"'>, Ts...>(f.name,
|
||||
",");
|
||||
|
||||
std::vector<X> i;
|
||||
|
||||
while (!p.eof()) {
|
||||
auto a = p.template get_next<int, double, std::string>();
|
||||
i.emplace_back(ss::to_object<X>(a));
|
||||
}
|
||||
|
||||
for (auto& [_, __, s] : i) {
|
||||
update_if_crlf(s);
|
||||
}
|
||||
CHECK_EQ(i, data);
|
||||
|
||||
auto [p_no_multiline, __] =
|
||||
make_parser<buffer_mode, ss::quote<'"'>, Ts...>(f.name, ",");
|
||||
while (!p.eof()) {
|
||||
auto command = [&p_no_multiline = p_no_multiline] {
|
||||
p_no_multiline.template get_next<int, double, std::string>();
|
||||
};
|
||||
expect_error_on_command(p_no_multiline, command);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parser test csv on multiple lines with quotes") {
|
||||
test_quote_multiline<false>();
|
||||
test_quote_multiline<false, ss::string_error>();
|
||||
test_quote_multiline<false, ss::throw_on_error>();
|
||||
test_quote_multiline<true>();
|
||||
test_quote_multiline<true, ss::string_error>();
|
||||
test_quote_multiline<true, ss::throw_on_error>();
|
||||
}
|
||||
|
||||
static inline std::string no_escape(std::string& s) {
|
||||
s.erase(std::remove(begin(s), end(s), '\\'), end(s));
|
||||
return s;
|
||||
}
|
||||
|
||||
template <bool buffer_mode, typename... Ts>
|
||||
void test_escape_multiline() {
|
||||
unique_file_name f{"test_parser"};
|
||||
std::vector<X> data = {{1, 2, "x\\\nx\\\r\nx"},
|
||||
{5, 6, "z\\\nz\\\nz"},
|
||||
{7, 8, "u"},
|
||||
{3, 4, "y\\\ny\\\ny"},
|
||||
{9, 10, "v\\\\"},
|
||||
{11, 12, "w\\\n"}};
|
||||
for (auto& [_, __, s] : data) {
|
||||
update_if_crlf(s);
|
||||
}
|
||||
|
||||
make_and_write(f.name, data);
|
||||
for (auto& [_, __, s] : data) {
|
||||
s = no_escape(s);
|
||||
if (s == "v") {
|
||||
s = "v\\";
|
||||
}
|
||||
}
|
||||
|
||||
auto [p, _] =
|
||||
make_parser<buffer_mode, ss::multiline, ss::escape<'\\'>, Ts...>(f.name,
|
||||
",");
|
||||
std::vector<X> i;
|
||||
|
||||
while (!p.eof()) {
|
||||
auto a = p.template get_next<int, double, std::string>();
|
||||
i.emplace_back(ss::to_object<X>(a));
|
||||
}
|
||||
|
||||
for (auto& [_, __, s] : i) {
|
||||
update_if_crlf(s);
|
||||
}
|
||||
CHECK_EQ(i, data);
|
||||
|
||||
auto [p_no_multiline, __] =
|
||||
make_parser<buffer_mode, ss::escape<'\\'>, Ts...>(f.name, ",");
|
||||
while (!p.eof()) {
|
||||
auto command = [&p_no_multiline = p_no_multiline] {
|
||||
auto a =
|
||||
p_no_multiline.template get_next<int, double, std::string>();
|
||||
};
|
||||
expect_error_on_command(p_no_multiline, command);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("parser test csv on multiple lines with escapes") {
|
||||
test_escape_multiline<false>();
|
||||
test_escape_multiline<false, ss::string_error>();
|
||||
test_escape_multiline<false, ss::throw_on_error>();
|
||||
test_escape_multiline<true>();
|
||||
test_escape_multiline<true, ss::string_error>();
|
||||
test_escape_multiline<true, ss::throw_on_error>();
|
||||
}
|
||||
|
||||
template <bool buffer_mode, typename... Ts>
|
||||
void test_quote_escape_multiline() {
|
||||
unique_file_name f{"test_parser"};
|
||||
{
|
||||
std::ofstream out{f.name};
|
||||
out << "1,2,\"just\\\n\nstrings\"" << std::endl;
|
||||
#ifndef _WIN32
|
||||
out << "3,4,\"just\r\nsome\\\r\n\n\\\nstrings\"" << std::endl;
|
||||
out << "5,6,\"just\\\n\\\r\n\r\n\nstrings" << std::endl;
|
||||
#else
|
||||
out << "3,4,\"just\nsome\\\n\n\\\nstrings\"" << std::endl;
|
||||
out << "5,6,\"just\\\n\\\n\n\nstrings" << std::endl;
|
||||
#endif
|
||||
out << "7,8,\"just strings\"" << std::endl;
|
||||
out << "9,10,just strings" << std::endl;
|
||||
}
|
||||
size_t bad_lines = 1;
|
||||
auto num_errors = 0;
|
||||
|
||||
auto [p, _] = make_parser<buffer_mode, ss::multiline, ss::escape<'\\'>,
|
||||
ss::quote<'"'>, Ts...>(f.name);
|
||||
std::vector<X> i;
|
||||
|
||||
while (!p.eof()) {
|
||||
try {
|
||||
auto a = p.template get_next<int, double, std::string>();
|
||||
if (p.valid()) {
|
||||
i.emplace_back(ss::to_object<X>(a));
|
||||
} else {
|
||||
++num_errors;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
++num_errors;
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(bad_lines == num_errors);
|
||||
|
||||
std::vector<X> data = {{1, 2, "just\n\nstrings"},
|
||||
#ifndef _WIN32
|
||||
{3, 4, "just\r\nsome\r\n\n\nstrings"},
|
||||
#else
|
||||
{3, 4, "just\nsome\n\n\nstrings"},
|
||||
#endif
|
||||
{9, 10, "just strings"}};
|
||||
|
||||
for (auto& [_, __, s] : i) {
|
||||
update_if_crlf(s);
|
||||
}
|
||||
CHECK_EQ(i, data);
|
||||
}
|
||||
|
||||
TEST_CASE("parser test csv on multiple lines with quotes and escapes") {
|
||||
test_quote_escape_multiline<false>();
|
||||
test_quote_escape_multiline<false, ss::string_error>();
|
||||
test_quote_escape_multiline<false, ss::throw_on_error>();
|
||||
test_quote_escape_multiline<true>();
|
||||
test_quote_escape_multiline<true, ss::string_error>();
|
||||
test_quote_escape_multiline<true, ss::throw_on_error>();
|
||||
}
|
||||
Reference in New Issue
Block a user