Compare commits

..

5 Commits

12 changed files with 359 additions and 232 deletions

View File

@ -56,7 +56,7 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}} run: ctest --output-on-failure
- name: Generate coverage report - name: Generate coverage report
run: | run: |

View File

@ -66,4 +66,4 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}} run: ctest --output-on-failure

View File

@ -56,4 +56,4 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}} run: ctest --output-on-failure

View File

@ -74,4 +74,4 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}} run: ctest --output-on-failure

View File

@ -62,4 +62,4 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: >- run: >-
ctest -C Debug --output-on-failure -j ${{steps.cores.outputs.count}} ctest -C Debug --output-on-failure

View File

@ -73,4 +73,4 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}} run: ctest --output-on-failure

View File

@ -71,4 +71,4 @@ jobs:
- name: Run - name: Run
working-directory: build working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}} run: ctest --output-on-failure

View File

@ -733,10 +733,10 @@ private:
return -1; return -1;
} }
c = buffer[curr_char++];
if (curr_char >= csv_data_size) { if (curr_char >= csv_data_size) {
return -1; return -1;
} }
c = buffer[curr_char++];
// TODO maybe remove this too // TODO maybe remove this too
if (*lineptr == nullptr) { if (*lineptr == nullptr) {

131
ssp.hpp
View File

@ -639,12 +639,12 @@ inline void assert_throw_on_error_not_defined() {
} }
#if __unix__ #if __unix__
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) { inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
return getline(lineptr, n, stream); return getline(lineptr, n, stream);
} }
#else #else
using ssize_t = int64_t; using ssize_t = int64_t;
inline ssize_t get_line(char** lineptr, size_t* n, FILE* stream) { inline ssize_t get_line_file(char** lineptr, size_t* n, FILE* stream) {
size_t pos; size_t pos;
int c; int c;
@ -2174,6 +2174,18 @@ public:
} }
} }
parser(const char* const csv_data_buffer, size_t csv_data_size,
const std::string& delim = ss::default_delimiter)
: file_name_{"buffer line"},
reader_{csv_data_buffer, csv_data_size, delim} {
read_line();
if constexpr (ignore_header) {
ignore_next();
} else {
raw_header_ = reader_.get_buffer();
}
}
parser(parser&& other) = default; parser(parser&& other) = default;
parser& operator=(parser&& other) = default; parser& operator=(parser&& other) = default;
@ -2767,18 +2779,27 @@ private:
: delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} { : delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} {
} }
reader(const char* const buffer, size_t csv_data_size,
const std::string& delim)
: delim_{delim}, csv_data_buffer_{buffer},
csv_data_size_{csv_data_size} {
}
reader(reader&& other) reader(reader&& other)
: buffer_{other.buffer_}, : buffer_{other.buffer_},
next_line_buffer_{other.next_line_buffer_}, next_line_buffer_{other.next_line_buffer_},
helper_buffer_{other.helper_buffer_}, converter_{std::move( helper_buffer_{other.helper_buffer_},
other.converter_)}, converter_{std::move(other.converter_)},
next_line_converter_{std::move(other.next_line_converter_)}, next_line_converter_{std::move(other.next_line_converter_)},
buffer_size_{other.buffer_size_}, buffer_size_{other.buffer_size_},
next_line_buffer_size_{other.next_line_buffer_size_}, next_line_buffer_size_{other.next_line_buffer_size_},
helper_size_{other.helper_size_}, delim_{std::move(other.delim_)}, helper_buffer_size{other.helper_buffer_size},
file_{other.file_}, crlf_{other.crlf_}, delim_{std::move(other.delim_)}, file_{other.file_},
line_number_{other.line_number_}, next_line_size_{ csv_data_buffer_{other.csv_data_buffer_},
other.next_line_size_} { csv_data_size_{other.csv_data_size_},
curr_char_{other.curr_char_}, crlf_{other.crlf_},
line_number_{other.line_number_},
next_line_size_{other.next_line_size_} {
other.buffer_ = nullptr; other.buffer_ = nullptr;
other.next_line_buffer_ = nullptr; other.next_line_buffer_ = nullptr;
other.helper_buffer_ = nullptr; other.helper_buffer_ = nullptr;
@ -2794,9 +2815,12 @@ private:
next_line_converter_ = std::move(other.next_line_converter_); next_line_converter_ = std::move(other.next_line_converter_);
buffer_size_ = other.buffer_size_; buffer_size_ = other.buffer_size_;
next_line_buffer_size_ = other.next_line_buffer_size_; next_line_buffer_size_ = other.next_line_buffer_size_;
helper_size_ = other.helper_size_; helper_buffer_size = other.helper_buffer_size;
delim_ = std::move(other.delim_); delim_ = std::move(other.delim_);
file_ = other.file_; file_ = other.file_;
csv_data_buffer_ = other.csv_data_buffer_;
csv_data_size_ = other.csv_data_size_;
curr_char_ = other.curr_char_;
crlf_ = other.crlf_; crlf_ = other.crlf_;
line_number_ = other.line_number_; line_number_ = other.line_number_;
next_line_size_ = other.next_line_size_; next_line_size_ = other.next_line_size_;
@ -2824,6 +2848,60 @@ private:
reader(const reader& other) = delete; reader(const reader& other) = delete;
reader& operator=(const reader& other) = delete; reader& operator=(const reader& other) = delete;
ssize_t get_line_buffer(char** lineptr, size_t* n,
const char* const buffer, size_t csv_data_size,
size_t& curr_char) {
size_t pos;
int c;
// TODO remove check
if (lineptr == nullptr || buffer == nullptr || n == nullptr) {
return -1;
}
if (curr_char >= csv_data_size) {
return -1;
}
c = buffer[curr_char++];
// TODO maybe remove this too
if (*lineptr == nullptr) {
*lineptr = static_cast<char*>(malloc(128));
if (*lineptr == nullptr) {
return -1;
}
*n = 128;
}
pos = 0;
while (curr_char <= csv_data_size) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
// TODO maybe remove this too
if (new_size < 128) {
new_size = 128;
}
char* new_ptr = static_cast<char*>(
realloc(static_cast<void*>(*lineptr), new_size));
// TODO check for failed malloc in the callee
if (new_ptr == nullptr) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
}
(*lineptr)[pos++] = c;
if (c == '\n') {
break;
}
c = buffer[curr_char++];
}
(*lineptr)[pos] = '\0';
return pos;
}
// read next line each time in order to set eof_ // read next line each time in order to set eof_
bool read_next() { bool read_next() {
next_line_converter_.clear_error(); next_line_converter_.clear_error();
@ -2834,8 +2912,16 @@ private:
if (next_line_buffer_size_ > 0) { if (next_line_buffer_size_ > 0) {
next_line_buffer_[0] = '\0'; next_line_buffer_[0] = '\0';
} }
ssize = get_line(&next_line_buffer_, &next_line_buffer_size_,
file_); if (file_) {
ssize = get_line_file(&next_line_buffer_,
&next_line_buffer_size_, file_);
} else {
ssize = get_line_buffer(&next_line_buffer_,
&next_line_buffer_size_,
csv_data_buffer_, csv_data_size_,
curr_char_);
}
if (ssize == -1) { if (ssize == -1) {
return false; return false;
@ -2947,6 +3033,10 @@ private:
} }
size_t remove_eol(char*& buffer, size_t ssize) { size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') {
return ssize;
}
size_t size = ssize - 1; size_t size = ssize - 1;
if (ssize >= 2 && buffer[ssize - 2] == '\r') { if (ssize >= 2 && buffer[ssize - 2] == '\r') {
crlf_ = true; crlf_ = true;
@ -2977,8 +3067,17 @@ private:
bool append_next_line_to_buffer(char*& buffer, size_t& size) { bool append_next_line_to_buffer(char*& buffer, size_t& size) {
undo_remove_eol(buffer, size); undo_remove_eol(buffer, size);
ssize_t next_ssize = ssize_t next_ssize;
get_line(&helper_buffer_, &helper_size_, file_); if (file_) {
next_ssize =
get_line_file(&helper_buffer_, &helper_buffer_size, file_);
} else {
next_ssize =
get_line_buffer(&helper_buffer_, &helper_buffer_size,
csv_data_buffer_, csv_data_size_,
curr_char_);
}
if (next_ssize == -1) { if (next_ssize == -1) {
return false; return false;
} }
@ -3005,11 +3104,15 @@ private:
size_t buffer_size_{0}; size_t buffer_size_{0};
size_t next_line_buffer_size_{0}; size_t next_line_buffer_size_{0};
size_t helper_size_{0}; size_t helper_buffer_size{0};
std::string delim_; std::string delim_;
FILE* file_{nullptr}; FILE* file_{nullptr};
const char* csv_data_buffer_{nullptr};
size_t csv_data_size_{0};
size_t curr_char_{0};
bool crlf_{false}; bool crlf_{false};
size_t line_number_{0}; size_t line_number_{0};

View File

@ -34,12 +34,25 @@ struct buffer {
[[maybe_unused]] inline buffer buff; [[maybe_unused]] inline buffer buff;
std::string time_now_rand() {
srand(time(nullptr));
std::stringstream ss;
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
ss << std::put_time(&tm, "%d%m%Y%H%M%S");
srand(time(nullptr));
return ss.str() + std::to_string(rand());
}
struct unique_file_name { struct unique_file_name {
static inline int i = 0;
std::string name; std::string name;
unique_file_name(const std::string& test) { unique_file_name(const std::string& test) {
do { do {
name = std::tmpnam(nullptr) + std::string{"_random_test_"} + test; name = "random_" + test + "_" + std::to_string(i++) + "_" +
time_now_rand() + "_file.csv";
} while (std::filesystem::exists(name)); } while (std::filesystem::exists(name));
} }

View File

@ -1,6 +1,6 @@
#include "test_parser1.hpp" #include "test_parser1.hpp"
template <typename... Ts> template <bool buffer_mode, typename... Ts>
void test_multiline_restricted() { void test_multiline_restricted() {
unique_file_name f{"test_parser"}; unique_file_name f{"test_parser"};
{ {
@ -23,9 +23,9 @@ void test_multiline_restricted() {
auto bad_lines = 15; auto bad_lines = 15;
auto num_errors = 0; auto num_errors = 0;
ss::parser<ss::multiline_restricted<2>, ss::quote<'"'>, ss::escape<'\\'>, auto [p, _] =
Ts...> make_parser<buffer_mode, ss::multiline_restricted<2>, ss::quote<'"'>,
p{f.name, ","}; ss::escape<'\\'>, Ts...>(f.name, ",");
std::vector<X> i; std::vector<X> i;
while (!p.eof()) { while (!p.eof()) {
@ -64,12 +64,15 @@ void test_multiline_restricted() {
} }
TEST_CASE("parser test multiline restricted") { TEST_CASE("parser test multiline restricted") {
test_multiline_restricted(); test_multiline_restricted<false>();
test_multiline_restricted<ss::string_error>(); test_multiline_restricted<false, ss::string_error>();
test_multiline_restricted<ss::throw_on_error>(); test_multiline_restricted<false, ss::throw_on_error>();
test_multiline_restricted<true>();
test_multiline_restricted<true, ss::string_error>();
test_multiline_restricted<true, ss::throw_on_error>();
} }
template <typename... Ts> template <bool buffer_mode, typename... Ts>
void test_unterminated_line_impl(const std::vector<std::string>& lines, void test_unterminated_line_impl(const std::vector<std::string>& lines,
size_t bad_line) { size_t bad_line) {
unique_file_name f{"test_parser"}; unique_file_name f{"test_parser"};
@ -79,10 +82,12 @@ void test_unterminated_line_impl(const std::vector<std::string>& lines,
} }
out.close(); out.close();
ss::parser<Ts...> p{f.name}; auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
size_t line = 0; size_t line = 0;
while (!p.eof()) { while (!p.eof()) {
auto command = [&] { p.template get_next<int, double, std::string>(); }; auto command = [&p = p] {
p.template get_next<int, double, std::string>();
};
if (line == bad_line) { if (line == bad_line) {
expect_error_on_command(p, command); expect_error_on_command(p, command);
@ -98,9 +103,15 @@ void test_unterminated_line_impl(const std::vector<std::string>& lines,
template <typename... Ts> template <typename... Ts>
void test_unterminated_line(const std::vector<std::string>& lines, void test_unterminated_line(const std::vector<std::string>& lines,
size_t bad_line) { size_t bad_line) {
test_unterminated_line_impl<Ts...>(lines, bad_line); test_unterminated_line_impl<false, Ts...>(lines, bad_line);
test_unterminated_line_impl<Ts..., ss::string_error>(lines, bad_line); test_unterminated_line_impl<false, Ts..., ss::string_error>(lines,
test_unterminated_line_impl<Ts..., ss::throw_on_error>(lines, bad_line); bad_line);
test_unterminated_line_impl<false, Ts..., ss::throw_on_error>(lines,
bad_line);
test_unterminated_line_impl<true, Ts...>(lines, bad_line);
test_unterminated_line_impl<true, Ts..., ss::string_error>(lines, bad_line);
test_unterminated_line_impl<true, Ts..., ss::throw_on_error>(lines,
bad_line);
} }
TEST_CASE("parser test csv on multiline with errors") { TEST_CASE("parser test csv on multiline with errors") {
@ -317,199 +328,3 @@ TEST_CASE("parser test csv on multiline with errors") {
test_unterminated_line<multiline, escape, quote>(lines, 1); test_unterminated_line<multiline, escape, quote>(lines, 1);
} }
} }
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>...> {};
static inline void check_size(size_t size1, size_t size2) {
CHECK_EQ(size1, size2);
}
template <typename Setup, typename... Ts>
static void test_fields_impl(const std::string file_name,
const std::vector<X>& data,
const std::vector<std::string>& fields) {
using CaseType = std::tuple<Ts...>;
ss::parser<Setup> p{file_name, ","};
CHECK_FALSE(p.field_exists("Unknown"));
p.use_fields(fields);
std::vector<CaseType> i;
for (const auto& a : p.template iterate<CaseType>()) {
i.push_back(a);
}
check_size(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);
}
}
}
template <typename... Ts>
static void test_fields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& fields) {
test_fields_impl<ss::setup<>, Ts...>(file_name, data, fields);
test_fields_impl<ss::setup<ss::string_error>, Ts...>(file_name, data,
fields);
test_fields_impl<ss::setup<ss::throw_on_error>, Ts...>(file_name, data,
fields);
}
TEST_CASE("parser test various cases with header") {
unique_file_name f{"test_parser"};
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, ","};
CHECK_FALSE(p.field_exists("Unknown"));
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());
}
{
ss::parser<ss::string_error> p{f.name, ","};
p.use_fields(Int, Dbl);
{
auto [int_, double_] = p.get_next<int, double>();
CHECK_EQ(int_, data[0].i);
CHECK_EQ(double_, data[0].d);
}
p.use_fields(Dbl, Int);
{
auto [double_, int_] = p.get_next<double, int>();
CHECK_EQ(int_, data[1].i);
CHECK_EQ(double_, data[1].d);
}
p.use_fields(Str);
{
auto string_ = p.get_next<std::string>();
CHECK_EQ(string_, data[2].s);
}
p.use_fields(Str, Int, Dbl);
{
auto [string_, int_, double_] =
p.get_next<std::string, int, double>();
CHECK_EQ(double_, data[3].d);
CHECK_EQ(int_, data[3].i);
CHECK_EQ(string_, data[3].s);
}
}
/* 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)
*/
test_fields<str>(o, d, {Str});
test_fields<int>(o, d, {Int});
test_fields<double>(o, d, {Dbl});
test_fields<str, int>(o, d, {Str, Int});
test_fields<str, double>(o, d, {Str, Dbl});
test_fields<int, str>(o, d, {Int, Str});
test_fields<int, double>(o, d, {Int, Dbl});
test_fields<double, str>(o, d, {Dbl, Str});
test_fields<double, int>(o, d, {Dbl, Int});
test_fields<str, int, double>(o, d, {Str, Int, Dbl});
test_fields<str, double, int>(o, d, {Str, Dbl, Int});
test_fields<int, str, double>(o, d, {Int, Str, Dbl});
test_fields<int, double, str>(o, d, {Int, Dbl, Str});
test_fields<double, str, int>(o, d, {Dbl, Str, Int});
test_fields<double, int, str>(o, d, {Dbl, Int, Str});
}

View File

@ -1,5 +1,201 @@
#include "test_parser1.hpp" #include "test_parser1.hpp"
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>...> {};
static inline void check_size(size_t size1, size_t size2) {
CHECK_EQ(size1, size2);
}
template <typename Setup, typename... Ts>
static void test_fields_impl(const std::string file_name,
const std::vector<X>& data,
const std::vector<std::string>& fields) {
using CaseType = std::tuple<Ts...>;
ss::parser<Setup> p{file_name, ","};
CHECK_FALSE(p.field_exists("Unknown"));
p.use_fields(fields);
std::vector<CaseType> i;
for (const auto& a : p.template iterate<CaseType>()) {
i.push_back(a);
}
check_size(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);
}
}
}
template <typename... Ts>
static void test_fields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& fields) {
test_fields_impl<ss::setup<>, Ts...>(file_name, data, fields);
test_fields_impl<ss::setup<ss::string_error>, Ts...>(file_name, data,
fields);
test_fields_impl<ss::setup<ss::throw_on_error>, Ts...>(file_name, data,
fields);
}
TEST_CASE("parser test various cases with header") {
unique_file_name f{"test_parser"};
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, ","};
CHECK_FALSE(p.field_exists("Unknown"));
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());
}
{
ss::parser<ss::string_error> p{f.name, ","};
p.use_fields(Int, Dbl);
{
auto [int_, double_] = p.get_next<int, double>();
CHECK_EQ(int_, data[0].i);
CHECK_EQ(double_, data[0].d);
}
p.use_fields(Dbl, Int);
{
auto [double_, int_] = p.get_next<double, int>();
CHECK_EQ(int_, data[1].i);
CHECK_EQ(double_, data[1].d);
}
p.use_fields(Str);
{
auto string_ = p.get_next<std::string>();
CHECK_EQ(string_, data[2].s);
}
p.use_fields(Str, Int, Dbl);
{
auto [string_, int_, double_] =
p.get_next<std::string, int, double>();
CHECK_EQ(double_, data[3].d);
CHECK_EQ(int_, data[3].i);
CHECK_EQ(string_, data[3].s);
}
}
/* 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)
*/
test_fields<str>(o, d, {Str});
test_fields<int>(o, d, {Int});
test_fields<double>(o, d, {Dbl});
test_fields<str, int>(o, d, {Str, Int});
test_fields<str, double>(o, d, {Str, Dbl});
test_fields<int, str>(o, d, {Int, Str});
test_fields<int, double>(o, d, {Int, Dbl});
test_fields<double, str>(o, d, {Dbl, Str});
test_fields<double, int>(o, d, {Dbl, Int});
test_fields<str, int, double>(o, d, {Str, Int, Dbl});
test_fields<str, double, int>(o, d, {Str, Dbl, Int});
test_fields<int, str, double>(o, d, {Int, Str, Dbl});
test_fields<int, double, str>(o, d, {Int, Dbl, Str});
test_fields<double, str, int>(o, d, {Dbl, Str, Int});
test_fields<double, int, str>(o, d, {Dbl, Int, Str});
}
template <typename... Ts> template <typename... Ts>
void test_invalid_fields_impl(const std::vector<std::string>& lines, void test_invalid_fields_impl(const std::vector<std::string>& lines,
const std::vector<std::string>& fields) { const std::vector<std::string>& fields) {