add ignore_header setup option, add unit tests for parsing by header, add string_view to possible conversion values

This commit is contained in:
ado
2022-03-27 21:04:02 +02:00
parent 45d166f93d
commit a8fa5c753c
69 changed files with 4194 additions and 33 deletions

View File

@@ -1,9 +1,9 @@
test_sources = files([
'test_main.cpp',
'test_splitter.cpp',
'test_converter.cpp',
#'test_splitter.cpp',
#'test_converter.cpp',
'test_parser.cpp',
'test_extractions.cpp',
#'test_extractions.cpp',
])
doctest_proj = subproject('doctest')

View File

@@ -107,6 +107,13 @@ TEST_CASE("converter test valid conversions") {
REQUIRE(std::holds_alternative<double>(std::get<0>(tup)));
CHECK_EQ(tup, std::make_tuple(std::variant<int, double>{5.5}, 6.6));
}
{
auto tup = c.convert<void, std::string_view, double,
std::string_view>("junk;s1;6.6;s2", ";");
REQUIRE(c.valid());
CHECK_EQ(tup, std::make_tuple(std::string_view{"s1"}, 6.6,
std::string_view{"s2"}));
}
}
TEST_CASE("converter test invalid conversions") {

View File

@@ -146,7 +146,7 @@ TEST_CASE("extract test functions for boolean values") {
CHECK_EQ(v, b);
}
for (const std::string& s : {"2", "tru", "truee", "xxx", ""}) {
for (const std::string s : {"2", "tru", "truee", "xxx", ""}) {
bool v;
CHECK_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
}
@@ -160,7 +160,7 @@ TEST_CASE("extract test functions for char values") {
CHECK_EQ(v, c);
}
for (const std::string& s : {"aa", "xxx", ""}) {
for (const std::string s : {"aa", "xxx", ""}) {
char v;
CHECK_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
}
@@ -187,13 +187,13 @@ TEST_CASE("extract test functions for std::optional") {
CHECK_EQ(*v, c);
}
for (const std::string& s : {"aa", "xxx", ""}) {
for (const std::string s : {"aa", "xxx", ""}) {
std::optional<int> v;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
CHECK_FALSE(v.has_value());
}
for (const std::string& s : {"aa", "xxx", ""}) {
for (const std::string s : {"aa", "xxx", ""}) {
std::optional<char> v;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), v));
CHECK_FALSE(v.has_value());

View File

@@ -21,9 +21,12 @@ struct unique_file_name {
unique_file_name()
: name{"random_" + std::to_string(i++) + time_now_rand() +
"_file.csv"} {}
"_file.csv"} {
}
~unique_file_name() { std::filesystem::remove(name); }
~unique_file_name() {
std::filesystem::remove(name);
}
};
void replace_all(std::string& s, const std::string& from,
@@ -57,7 +60,9 @@ struct X {
.append(delim)
.append(s);
}
auto tied() const { return std::tie(i, d, s); }
auto tied() const {
return std::tie(i, d, s);
}
};
template <typename T>
@@ -68,7 +73,8 @@ std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(const T& lhs,
template <typename T>
static void make_and_write(const std::string& file_name,
const std::vector<T>& data) {
const std::vector<T>& data,
const std::vector<std::string>& header = {}) {
std::ofstream out{file_name};
#ifdef _WIN32
@@ -77,6 +83,17 @@ static void make_and_write(const std::string& file_name,
std::vector<const char*> new_lines = {"\n", "\r\n"};
#endif
for (const auto& i : header) {
if (&i != &header.front()) {
out << T::delim;
}
out << i;
}
if (!header.empty()) {
out << new_lines.front();
}
for (size_t i = 0; i < data.size(); ++i) {
out << data[i].to_string() << new_lines[i % new_lines.size()];
}
@@ -91,8 +108,6 @@ TEST_CASE("parser test various cases") {
make_and_write(f.name, data);
{
ss::parser<ss::string_error> p{f.name, ","};
ss::parser p0{std::move(p)};
p = std::move(p0);
std::vector<X> i;
ss::parser<ss::string_error> p2{f.name, ","};
@@ -322,10 +337,13 @@ struct test_struct {
int i;
double d;
char c;
auto tied() { return std::tie(i, d, c); }
auto tied() {
return std::tie(i, d, c);
}
};
void expect_test_struct(const test_struct&) {}
void expect_test_struct(const test_struct&) {
}
// various scenarios
TEST_CASE("parser test composite conversion") {
@@ -546,7 +564,9 @@ struct my_string {
my_string() = default;
~my_string() { delete[] data; }
~my_string() {
delete[] data;
}
// make sure no object is copied
my_string(const my_string&) = delete;
@@ -577,7 +597,9 @@ struct xyz {
my_string x;
my_string y;
my_string z;
auto tied() { return std::tie(x, y, z); }
auto tied() {
return std::tie(x, y, z);
}
};
TEST_CASE("parser test the moving of parsed values") {
@@ -832,3 +854,149 @@ TEST_CASE("parser test multiline restricted") {
}
CHECK_EQ(i, data);
}
template <typename T, typename Tuple>
struct has_type;
template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>>
: std::disjunction<std::is_same<T, Us>...> {};
void checkSize(size_t size1, size_t size2) {
CHECK_EQ(size1, size2);
}
template <typename... Ts>
void testFields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& fields) {
using CaseType = std::tuple<Ts...>;
ss::parser p{file_name, ","};
p.use_fields(fields);
std::vector<CaseType> i;
for (const auto& a : p.iterate<CaseType>()) {
i.push_back(a);
}
checkSize(i.size(), data.size());
for (size_t j = 0; j < i.size(); ++j) {
if constexpr (has_type<int, CaseType>::value) {
CHECK_EQ(std::get<int>(i[j]), data[j].i);
}
if constexpr (has_type<double, CaseType>::value) {
CHECK_EQ(std::get<double>(i[j]), data[j].d);
}
if constexpr (has_type<std::string, CaseType>::value) {
CHECK_EQ(std::get<std::string>(i[j]), data[j].s);
}
}
}
TEST_CASE("parser test various cases with header") {
unique_file_name f;
constexpr static auto Int = "Int";
constexpr static auto Dbl = "Double";
constexpr static auto Str = "String";
using str = std::string;
std::vector<std::string> header{Int, Dbl, Str};
std::vector<X> 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, header);
const auto& o = f.name;
const auto& d = data;
{
ss::parser<ss::string_error> p{f.name, ","};
std::vector<X> i;
for (const auto& a : p.iterate<int, double, std::string>()) {
i.emplace_back(ss::to_object<X>(a));
}
CHECK_NE(i, data);
}
{
ss::parser<ss::string_error> p{f.name, ","};
std::vector<X> i;
p.ignore_next();
for (const auto& a : p.iterate<int, double, std::string>()) {
i.emplace_back(ss::to_object<X>(a));
}
CHECK_EQ(i, data);
}
{
ss::parser<ss::ignore_header> p{f.name, ","};
std::vector<X> i;
for (const auto& a : p.iterate<int, double, std::string>()) {
i.emplace_back(ss::to_object<X>(a));
}
CHECK_EQ(i, data);
}
{
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
p.use_fields(Int, Dbl, Str);
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
p.use_fields(Int, "Unknown");
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::ignore_header, ss::string_error> p{f.name, ","};
p.use_fields(Int, Int);
CHECK_FALSE(p.valid());
}
/* python used to generate permutations
import itertools
header = {'str': 'Str',
'double': 'Dbl',
'int': 'Int'}
keys = ['str', 'int', 'double']
for r in range (1, 3):
combinations = list(itertools.permutations(keys, r = r))
for combination in combinations:
template_params = []
arg_params = []
for type in combination:
template_params.append(type)
arg_params.append(header[type])
call = 'testFields<' + ', '.join(template_params) + \
'>(o, d, {' + ', '.join(arg_params) + '});'
print(call)
*/
testFields<str>(o, d, {Str});
testFields<int>(o, d, {Int});
testFields<double>(o, d, {Dbl});
testFields<str, int>(o, d, {Str, Int});
testFields<str, double>(o, d, {Str, Dbl});
testFields<int, str>(o, d, {Int, Str});
testFields<int, double>(o, d, {Int, Dbl});
testFields<double, str>(o, d, {Dbl, Str});
testFields<double, int>(o, d, {Dbl, Int});
testFields<str, int, double>(o, d, {Str, Int, Dbl});
testFields<str, double, int>(o, d, {Str, Dbl, Int});
testFields<int, str, double>(o, d, {Int, Str, Dbl});
testFields<int, double, str>(o, d, {Int, Dbl, Str});
testFields<double, str, int>(o, d, {Dbl, Str, Int});
testFields<double, int, str>(o, d, {Dbl, Int, Str});
}