#ifndef _TEST_CASE_HPP_ #define _TEST_CASE_HPP_ #ifdef RYML_SINGLE_HEADER #include #else #include "c4/std/vector.hpp" #include "c4/std/string.hpp" #include "c4/format.hpp" #include #include #include #endif #include #include // no pragma push for these warnings! they will be suppressed in the // files including this header (most test files) #ifdef __clang__ # pragma clang diagnostic ignored "-Wold-style-cast" #elif defined(__GNUC__) # pragma GCC diagnostic ignored "-Wold-style-cast" #endif #ifdef __clang__ # pragma clang diagnostic push #elif defined(__GNUC__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wtype-limits" #elif defined(_MSC_VER) # pragma warning(push) # pragma warning(disable: 4296/*expression is always 'boolean_value'*/) # pragma warning(disable: 4389/*'==': signed/unsigned mismatch*/) # pragma warning(disable: 4702/*unreachable code*/) # if C4_MSVC_VERSION != C4_MSVC_VERSION_2017 # pragma warning(disable: 4800/*'int': forcing value to bool 'true' or 'false' (performance warning)*/) # endif #endif #ifdef RYML_DBG # include #endif #include "test_lib/test_case_node.hpp" /** @todo use a matcher and EXPECT_THAT(): * see http://google.github.io/googletest/reference/assertions.html#EXPECT_THAT * see http://google.github.io/googletest/gmock_cook_book.html#NewMatchers */ #define RYML_COMPARE_NODE_TYPE(lhs, rhs, op, testop) \ do \ { \ if(!((lhs) op (rhs))) \ { \ char ltypebuf[256]; \ char rtypebuf[256]; \ csubstr ltype = NodeType::type_str(ltypebuf, (NodeType_e)lhs); \ csubstr rtype = NodeType::type_str(rtypebuf, (NodeType_e)rhs); \ if(ltype.str && rtype.str) \ { \ EXPECT_##testop(lhs, rhs) \ << " " << ltype.str << " (" << (lhs) << ")" << "=" << #lhs \ << "\n" \ << " " << rtype.str << " (" << (rhs) << ")" << "=" << #rhs; \ } \ else \ { \ EXPECT_##testop(lhs, rhs) \ << "(type too large to fit print buffer)"; \ } \ } \ } while(0) namespace c4 { inline void PrintTo(substr s, ::std::ostream* os) { *os << "'"; os->write(s.str, (std::streamsize)s.len); *os << "'"; } inline void PrintTo(csubstr s, ::std::ostream* os) { *os << "'"; os->write(s.str, (std::streamsize)s.len); *os << "'"; } namespace yml { #define RYML_TRACE_FMT(fmt, ...) SCOPED_TRACE([&]{ return formatrs(fmt, __VA_ARGS__); }()) inline void PrintTo(NodeType ty, ::std::ostream* os) { *os << ty.type_str(); } inline void PrintTo(NodeType_e ty, ::std::ostream* os) { *os << NodeType::type_str(ty); } inline void PrintTo(Callbacks const& cb, ::std::ostream* os) { #ifdef __GNUC__ #define RYML_GNUC_EXTENSION __extension__ #else #define RYML_GNUC_EXTENSION #endif *os << '{' << "userdata." << (void*)cb.m_user_data << ',' << "allocate." << RYML_GNUC_EXTENSION (void*)cb.m_allocate << ',' << "free." << RYML_GNUC_EXTENSION (void*)cb.m_free << ',' << "error_basic." << RYML_GNUC_EXTENSION (void*)cb.m_error_basic << '}' << "error_parse." << RYML_GNUC_EXTENSION (void*)cb.m_error_parse << '}' << "error_visit." << RYML_GNUC_EXTENSION (void*)cb.m_error_visit << '}'; #undef RYML_GNUC_EXTENSION } struct Case; struct TestCaseNode; struct CaseData; Case const* get_case(csubstr name); CaseData* get_data(csubstr name); void test_compare(Tree const& actual, Tree const& expected, const char *actual_name="actual", const char *expected_name="expected"); void test_compare(Tree const& actual, id_type node_actual, Tree const& expected, id_type node_expected, id_type level=0, const char *actual_name="actual", const char *expected_name="expected"); void test_arena_not_shared(Tree const& a, Tree const& b); void test_invariants(Tree const& t); void test_invariants(ConstNodeRef const& n); void print_test_node(TestCaseNode const& t, int level=0); void print_test_tree(TestCaseNode const& p, int level=0); void print_test_tree(const char *message, TestCaseNode const& t); void print_path(ConstNodeRef const& p); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template void test_check_emit_check_with_parser(Tree const& t, Parser &parser, CheckFn &&check_fn) { #ifdef RYML_DBG print_tree(t); #endif { SCOPED_TRACE("original yaml"); test_invariants(t); std::forward(check_fn)(t, parser); } auto emit_and_parse = [&](Tree const& tp, const char* identifier){ SCOPED_TRACE(identifier); std::string emitted = emitrs_yaml(tp); #ifdef RYML_DBG printf("~~~%s~~~[%zu]\n%.*s", identifier, emitted.size(), (int)emitted.size(), emitted.data()); #endif Tree cp = parse_in_arena(&parser, to_csubstr(emitted)); #ifdef RYML_DBG print_tree(cp); #endif test_invariants(cp); std::forward(check_fn)(cp, parser); return cp; }; Tree cp = emit_and_parse(t, "emitted 1"); cp = emit_and_parse(cp, "emitted 2"); cp = emit_and_parse(cp, "emitted 3"); } template void test_check_emit_check(Tree const& t, Parser &parser, CheckFn &&check_fn) { test_check_emit_check_with_parser(t, parser, [&check_fn](Tree const& t_, Parser const&){ std::forward(check_fn)(t_); }); } template void test_check_emit_check_with_parser(csubstr yaml, ParserOptions opts, CheckFn check_fn) { Parser::handler_type evt_handler = {}; Parser parser(&evt_handler, opts); const Tree t = parse_in_arena(&parser, yaml); test_check_emit_check_with_parser(t, parser, std::forward(check_fn)); } template void test_check_emit_check(csubstr yaml, ParserOptions opts, CheckFn &&check_fn) { Parser::handler_type evt_handler = {}; Parser parser(&evt_handler, opts); const Tree t = parse_in_arena(&parser, yaml); test_check_emit_check(t, parser, std::forward(check_fn)); } template void test_check_emit_check_with_parser(Tree const& t, CheckFn &&check_fn) { Parser::handler_type evt_handler = {}; Parser parser(&evt_handler, ParserOptions()); test_check_emit_check_with_parser(t, parser, check_fn); } template void test_check_emit_check(Tree const& t, CheckFn &&check_fn) { Parser::handler_type evt_handler = {}; Parser parser(&evt_handler, ParserOptions()); test_check_emit_check(t, parser, std::forward(check_fn)); } template void test_check_emit_check_with_parser(csubstr yaml, CheckFn &&check_fn) { test_check_emit_check_with_parser(yaml, ParserOptions(), std::forward(check_fn)); } template void test_check_emit_check(csubstr yaml, CheckFn &&check_fn) { test_check_emit_check(yaml, ParserOptions(), std::forward(check_fn)); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- inline c4::substr replace_all(c4::csubstr pattern, c4::csubstr repl, c4::csubstr subject, std::string *dst) { _RYML_CHECK_BASIC(!subject.overlaps(to_csubstr(*dst))); size_t ret = subject.replace_all(to_substr(*dst), pattern, repl); if(ret != dst->size()) { dst->resize(ret); ret = subject.replace_all(to_substr(*dst), pattern, repl); } _RYML_CHECK_BASIC(ret == dst->size()); return c4::to_substr(*dst); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- struct ExpectError { bool m_got_an_error; Tree *m_tree; c4::yml::Callbacks m_glob_prev; c4::yml::Callbacks m_tree_prev; Location expected_location; ExpectError(Location loc={}) : ExpectError(nullptr, loc) {} ExpectError(Tree *tree, Location loc={}); ~ExpectError(); using fntestref = std::function const&; static void check_success( fntestref fn) { check_success(nullptr, fn); }; static void check_success(Tree *tree, fntestref fn); static void check_error_basic( fntestref fn) { check_error_basic((const Tree*)nullptr, fn); } static void check_error_basic(Tree *tree, fntestref fn); static void check_error_basic(Tree const *tree, fntestref fn); static void check_assert_basic( fntestref fn) { check_assert_parse(nullptr, fn); } static void check_assert_basic(Tree *tree, fntestref fn); static void check_error_parse( fntestref fn, Location const& expected={}) { check_error_parse((const Tree*)nullptr, fn, expected); } static void check_error_parse(Tree *tree, fntestref fn, Location const& expected={}); static void check_error_parse(Tree const *tree, fntestref fn, Location const& expected={}); static void check_assert_parse( fntestref fn, Location const& expected={}) { check_assert_parse(nullptr, fn, expected); } static void check_assert_parse(Tree *tree, fntestref fn, Location const& expected={}); static void check_error_visit( fntestref fn, id_type id=npos) { check_error_visit((const Tree*)nullptr, fn, id); } static void check_error_visit(Tree *tree, fntestref fn, id_type id=npos); static void check_error_visit(Tree const *tree, fntestref fn, id_type id=npos); static void check_assert_visit( fntestref fn, id_type id=npos) { check_assert_visit(nullptr, fn, id); } static void check_assert_visit(Tree *tree, fntestref fn, id_type id=npos); }; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- typedef enum { EXPECT_PARSE_ERROR = (1<<0), RESOLVE_REFS = (1<<1), EXPECT_RESOLVE_ERROR = (1<<2), JSON_WRITE = (1<<3), // TODO: make it the opposite: opt-out instead of opt-in JSON_READ = (1<<4), HAS_CONTAINER_KEYS = (1<<5), HAS_MULTILINE_SCALAR = (1<<6), } TestCaseFlags_e; struct Case { std::string filelinebuf; csubstr fileline; csubstr name; csubstr src; TestCaseNode root; TestCaseFlags_e flags; Location expected_location; //! create a test case with an error on an expected location Case(csubstr file, int line, const char *name_, int f_, const char *src_, Location const& loc={}) : filelinebuf(catrs(file, ':', line)), fileline(to_csubstr(filelinebuf)), name(to_csubstr(name_)), src(to_csubstr(src_)), root(), flags((TestCaseFlags_e)f_), expected_location(name, loc.line, loc.col) {} //! create a standard test case: name, source and expected CaseNode structure Case(csubstr file, int line, const char *name_, const char *src_, TestCaseNode&& node) : /* */filelinebuf(catrs(file, ':', line)), fileline(to_csubstr(filelinebuf)), name(to_csubstr(name_)), src(to_csubstr(src_)), root(std::move(node)), flags(), expected_location() {} //! create a test case with explicit flags: name, source flags, and expected CaseNode structure Case(csubstr file, int line, const char *name_, int f_, const char *src_, TestCaseNode&& node) : /* */filelinebuf(catrs(file, ':', line)), fileline(to_csubstr(filelinebuf)), name(to_csubstr(name_)), src(to_csubstr(src_)), root(std::move(node)), flags((TestCaseFlags_e)f_), expected_location() {} }; //----------------------------------------------------------------------------- // a persistent data store to avoid repeating operations on every test struct CaseDataLineEndings { void assign(csubstr src_orig) { parse_buf_ints.assign(src_orig.begin(), src_orig.end()); src_buf.assign(src_orig.begin(), src_orig.end()); src = to_substr(src_buf); } std::vector src_buf; substr src; Tree parsed_tree{0}; size_t numbytes_stdout; size_t numbytes_stdout_json; std::string emit_buf; csubstr emitted_yml; std::string emitjson_buf; csubstr emitted_json; std::string parse_buf; substr parsed_yml; std::string parse_buf_json; substr parsed_json; Tree emitted_tree{0}; Tree emitted_tree_json{0}; Tree recreated{0}; std::string parse_buf_ints; std::vector parsed_ints; std::vector arena_ints; }; struct CaseData { CaseDataLineEndings unix_style; CaseDataLineEndings unix_style_json; CaseDataLineEndings windows_style; CaseDataLineEndings windows_style_json; }; } // namespace yml } // namespace c4 #ifdef __clang__ # pragma clang diagnostic pop #elif defined(__GNUC__) # pragma GCC diagnostic pop #elif defined(_MSC_VER) # pragma warning(pop) #endif #endif /* _TEST_CASE_HPP_ */