From 9f4424d7db3c61e7e67ecd73bfad7743504ddfb7 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 24 Nov 2024 19:38:03 +0100 Subject: [PATCH 1/2] test: Include file in error message Also move the error reporting to a single function --- test/boostLocale/test/unit_test.hpp | 160 ++++++++++++++-------------- test/test_catalog.cpp | 2 +- test/test_date_time.cpp | 2 +- test/test_formatting.cpp | 19 ++-- 4 files changed, 93 insertions(+), 90 deletions(-) diff --git a/test/boostLocale/test/unit_test.hpp b/test/boostLocale/test/unit_test.hpp index 81db1829..953f414f 100644 --- a/test/boostLocale/test/unit_test.hpp +++ b/test/boostLocale/test/unit_test.hpp @@ -23,6 +23,12 @@ # include #endif +#ifndef BOOST_LOCALE_ERROR_LIMIT +# define BOOST_LOCALE_ERROR_LIMIT 20 +#endif + +#define BOOST_LOCALE_STRINGIZE(x) #x + namespace boost { namespace locale { namespace test { /// Name/path of current executable std::string exe_name; @@ -48,50 +54,47 @@ namespace boost { namespace locale { namespace test { static test_result instance; return instance; } -}}} // namespace boost::locale::test -#ifndef BOOST_LOCALE_ERROR_LIMIT -# define BOOST_LOCALE_ERROR_LIMIT 20 -#endif + inline void report_error(const char* expr, const char* file, int line) + { + std::cerr << "Error at " << file << '#' << line << ": " << expr << std::endl; + if(++boost::locale::test::results().error_counter > BOOST_LOCALE_ERROR_LIMIT) + throw std::runtime_error("Error limits reached, stopping unit test"); + } +}}} // namespace boost::locale::test -#define BOOST_LOCALE_STRINGIZE(x) #x +#define BOOST_LOCALE_TEST_REPORT_ERROR(expr) boost::locale::test::report_error(expr, __FILE__, __LINE__) -#define THROW_IF_TOO_BIG(X) \ - if((X) > BOOST_LOCALE_ERROR_LIMIT) \ - throw std::runtime_error("Error limits reached, stopping unit test") - -#define TEST(X) \ - do { \ - boost::locale::test::results().test_counter++; \ - if(X) \ - break; \ - std::cerr << "Error in line:" << __LINE__ << " " #X << std::endl; \ - THROW_IF_TOO_BIG(boost::locale::test::results().error_counter++); \ - BOOST_LOCALE_START_CONST_CONDITION \ +#define TEST(X) \ + do { \ + boost::locale::test::results().test_counter++; \ + if(X) \ + break; \ + BOOST_LOCALE_TEST_REPORT_ERROR(#X); \ + BOOST_LOCALE_START_CONST_CONDITION \ } while(0) BOOST_LOCALE_END_CONST_CONDITION -#define TEST_REQUIRE(X) \ - do { \ - boost::locale::test::results().test_counter++; \ - if(X) \ - break; \ - std::cerr << "Error in line " << __LINE__ << ": " #X << std::endl; \ - throw std::runtime_error("Critical test " #X " failed"); \ - BOOST_LOCALE_START_CONST_CONDITION \ +#define TEST_REQUIRE(X) \ + do { \ + boost::locale::test::results().test_counter++; \ + if(X) \ + break; \ + BOOST_LOCALE_TEST_REPORT_ERROR(#X); \ + throw std::runtime_error("Critical test " #X " failed"); \ + BOOST_LOCALE_START_CONST_CONDITION \ } while(0) BOOST_LOCALE_END_CONST_CONDITION -#define TEST_THROWS(X, E) \ - do { \ - boost::locale::test::results().test_counter++; \ - try { \ - X; \ - } catch(E const& /*e*/) { \ - break; \ - } catch(...) { \ - } \ - std::cerr << "Error in line " << __LINE__ << ": " #X << std::endl; \ - THROW_IF_TOO_BIG(boost::locale::test::results().error_counter++); \ - BOOST_LOCALE_START_CONST_CONDITION \ +#define TEST_THROWS(X, E) \ + do { \ + boost::locale::test::results().test_counter++; \ + try { \ + X; \ + } catch(E const& /*e*/) { \ + break; \ + } catch(...) { \ + } \ + BOOST_LOCALE_TEST_REPORT_ERROR(#X); \ + BOOST_LOCALE_START_CONST_CONDITION \ } while(0) BOOST_LOCALE_END_CONST_CONDITION void test_main(int argc, char** argv); @@ -138,6 +141,11 @@ const std::string& to_string(const std::string& s) return s; } +std::string to_string(std::nullptr_t) +{ + return ""; +} + template std::string to_string(const std::vector& v) { @@ -223,58 +231,52 @@ std::string to_string(const char32_t c) #endif template -void test_impl(bool success, T const& l, U const& r, const char* expr, const char* fail_expr, int line) +void test_impl(bool success, + T const& l, + U const& r, + const char* expr, + const char* fail_expr, + const char* file, + int line) { boost::locale::test::results().test_counter++; if(!success) { - std::cerr << "Error in line " << line << ": " << expr << std::endl; - std::cerr << "---- [" << to_string(l) << "] " << fail_expr << " [" << to_string(r) << "]" << std::endl; - THROW_IF_TOO_BIG(boost::locale::test::results().error_counter++); + if(fail_expr) { + std::ostringstream s; + s << expr << '\n' << "---- [" << to_string(l) << "] " << fail_expr << " [" << to_string(r) << "]"; + boost::locale::test::report_error(s.str().c_str(), file, line); + } else + boost::locale::test::report_error(expr, file, line); } } -template -void test_eq_impl(T const& l, U const& r, const char* expr, int line) -{ - test_impl(l == r, l, r, expr, "!=", line); -} - -template -void test_ne_impl(T const& l, U const& r, const char* expr, int line) -{ - test_impl(l != r, l, r, expr, "==", line); -} - -template -void test_le_impl(T const& l, U const& r, const char* expr, int line) +void test_impl(bool success, const char* reason, const char* file, int line) { - test_impl(l <= r, l, r, expr, ">", line); + test_impl(success, nullptr, nullptr, reason, nullptr, file, line); } -template -void test_lt_impl(T const& l, U const& r, const char* expr, int line) -{ - test_impl(l < r, l, r, expr, ">=", line); -} - -template -void test_ge_impl(T const& l, U const& r, const char* expr, int line) -{ - test_impl(l >= r, l, r, expr, "<", line); -} - -template -void test_gt_impl(T const& l, U const& r, const char* expr, int line) -{ - test_impl(l > r, l, r, expr, "<=", line); -} +#define BOOST_LOCALE_TEST_OP_IMPL(name, test_op, fail_op) \ + template \ + void test_##name##_impl(T const& l, U const& r, const char* expr, const char* file, int line) \ + { \ + test_impl(l test_op r, l, r, expr, #fail_op, file, line); \ + } -#define TEST_EQ(x, y) test_eq_impl(x, y, BOOST_LOCALE_STRINGIZE(x == y), __LINE__) -#define TEST_NE(x, y) test_ne_impl(x, y, BOOST_LOCALE_STRINGIZE(x != y), __LINE__) -#define TEST_LE(x, y) test_le_impl(x, y, BOOST_LOCALE_STRINGIZE(x <= y), __LINE__) -#define TEST_LT(x, y) test_lt_impl(x, y, BOOST_LOCALE_STRINGIZE(x < y), __LINE__) -#define TEST_GE(x, y) test_ge_impl(x, y, BOOST_LOCALE_STRINGIZE(x >= y), __LINE__) -#define TEST_GT(x, y) test_gt_impl(x, y, BOOST_LOCALE_STRINGIZE(x > y), __LINE__) +BOOST_LOCALE_TEST_OP_IMPL(eq, ==, !=) +BOOST_LOCALE_TEST_OP_IMPL(ne, !=, ==) +BOOST_LOCALE_TEST_OP_IMPL(le, <=, >) +BOOST_LOCALE_TEST_OP_IMPL(lt, <, >=) +BOOST_LOCALE_TEST_OP_IMPL(ge, >=, <) +BOOST_LOCALE_TEST_OP_IMPL(gt, >, <=) + +#undef BOOST_LOCALE_TEST_OP_IMPL + +#define TEST_EQ(x, y) test_eq_impl(x, y, BOOST_LOCALE_STRINGIZE(x == y), __FILE__, __LINE__) +#define TEST_NE(x, y) test_ne_impl(x, y, BOOST_LOCALE_STRINGIZE(x != y), __FILE__, __LINE__) +#define TEST_LE(x, y) test_le_impl(x, y, BOOST_LOCALE_STRINGIZE(x <= y), __FILE__, __LINE__) +#define TEST_LT(x, y) test_lt_impl(x, y, BOOST_LOCALE_STRINGIZE(x < y), __FILE__, __LINE__) +#define TEST_GE(x, y) test_ge_impl(x, y, BOOST_LOCALE_STRINGIZE(x >= y), __FILE__, __LINE__) +#define TEST_GT(x, y) test_gt_impl(x, y, BOOST_LOCALE_STRINGIZE(x > y), __FILE__, __LINE__) #if BOOST_LOCALE_SPACESHIP_NULLPTR_WARNING # pragma clang diagnostic pop diff --git a/test/test_catalog.cpp b/test/test_catalog.cpp index 29a34c69..6b884b99 100644 --- a/test/test_catalog.cpp +++ b/test/test_catalog.cpp @@ -47,7 +47,7 @@ void test_plural_expr() TEST(ptr); \ return ptr; \ }() -#define TEST_EQ_EXPR(expr, rhs) test_eq_impl(COMPILE_PLURAL_EXPR(expr)(0), rhs, expr, __LINE__) +#define TEST_EQ_EXPR(expr, rhs) test_eq_impl(COMPILE_PLURAL_EXPR(expr)(0), rhs, expr, __FILE__, __LINE__) // Number only TEST_EQ_EXPR("0", 0); TEST_EQ_EXPR("42", 42); diff --git a/test/test_date_time.cpp b/test/test_date_time.cpp index b9e6ca0e..38280caf 100644 --- a/test/test_date_time.cpp +++ b/test/test_date_time.cpp @@ -30,7 +30,7 @@ #define TEST_EQ_FMT(t, X) \ empty_stream(ss) << (t); \ - test_eq_impl(ss.str(), X, #t "==" #X, __LINE__) + test_eq_impl(ss.str(), X, #t "==" #X, __FILE__, __LINE__) // Very simple container for a part of the tests. Counts its instances struct mock_calendar : public boost::locale::abstract_calendar { diff --git a/test/test_formatting.cpp b/test/test_formatting.cpp index d418c583..e2df2adc 100644 --- a/test/test_formatting.cpp +++ b/test/test_formatting.cpp @@ -159,17 +159,17 @@ void test_fmt_impl(std::basic_ostringstream& ss, const std::basic_string& expected, int line) { - ss << value; - test_eq_impl(ss.str(), expected, "", line); + test_impl(!!(ss << value), "Formatting failed", __FILE__, line); + test_eq_impl(ss.str(), expected, "", __FILE__, line); } template void test_parse_impl(std::basic_istringstream& ss, const T& expected, int line) { T v; - ss >> v >> std::ws; - test_eq_impl(v, expected, "v == expected", line); - test_eq_impl(ss.eof(), true, "ss.eof()", line); + test_impl(!!(ss >> v), "Parsing failed", __FILE__, line); + test_eq_impl(v, expected, "v == expected", __FILE__, line); + test_eq_impl((ss >> std::ws).eof(), true, "ss.eof()", __FILE__, line); } template @@ -178,8 +178,9 @@ void test_parse_at_impl(std::basic_istringstream& ss, const T& expecte T v; CharType c_at; ss >> v >> std::skipws >> c_at; - test_eq_impl(v, expected, "v == expected", line); - test_eq_impl(c_at, '@', "c_at == @", line); + test_impl(!!ss, "Parsing failed", __FILE__, line); + test_eq_impl(v, expected, "v == expected", __FILE__, line); + test_eq_impl(c_at, '@', "c_at == @", __FILE__, line); } template @@ -187,7 +188,7 @@ void test_parse_fail_impl(std::basic_istringstream& ss, int line) { T v; ss >> v; - test_eq_impl(ss.fail(), true, "ss.fail()", line); + test_eq_impl(ss.fail(), true, "ss.fail()", __FILE__, line); } #define TEST_FMT(manip, value, expected) \ @@ -641,7 +642,7 @@ void test_format_class_impl(const std::string& fmt_string, format_type fmt(std::basic_string(fmt_string.begin(), fmt_string.end())); fmt % value; std::basic_string expected_str_loc(to_correct_string(expected_str, loc)); - test_eq_impl(fmt.str(loc), expected_str_loc, ("Format: " + fmt_string).c_str(), line); + test_eq_impl(fmt.str(loc), expected_str_loc, ("Format: " + fmt_string).c_str(), __FILE__, line); } template From f0d95b079b4be457327350cddd35e76ef3ff4553 Mon Sep 17 00:00:00 2001 From: Alexander Grund Date: Sun, 24 Nov 2024 20:01:01 +0100 Subject: [PATCH 2/2] Add TEST_CONTEXT Avoid to require manual output in anticipation of failures. --- test/boostLocale/test/unit_test.hpp | 37 ++++++++++++++++++++++++++--- test/test_catalog.cpp | 6 ++--- test/test_codecvt.cpp | 4 +--- test/test_encoding.cpp | 14 +++++------ test/test_formatting.cpp | 2 ++ 5 files changed, 45 insertions(+), 18 deletions(-) diff --git a/test/boostLocale/test/unit_test.hpp b/test/boostLocale/test/unit_test.hpp index 953f414f..cbfc8cd4 100644 --- a/test/boostLocale/test/unit_test.hpp +++ b/test/boostLocale/test/unit_test.hpp @@ -10,6 +10,7 @@ #define BOOST_LOCALE_UNIT_TEST_HPP #include +#include #include #include #include @@ -33,8 +34,10 @@ namespace boost { namespace locale { namespace test { /// Name/path of current executable std::string exe_name; + class test_context; + struct test_result { - test_result() : error_counter(0), test_counter(0) + test_result() { #if defined(_MSC_VER) && (_MSC_VER > 1310) // disable message boxes on assert(), abort() @@ -46,8 +49,9 @@ namespace boost { namespace locale { namespace test { _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); #endif } - int error_counter; - int test_counter; + int error_counter = 0; + int test_counter = 0; + const test_context* context = nullptr; }; inline test_result& results() { @@ -55,9 +59,32 @@ namespace boost { namespace locale { namespace test { return instance; } + class test_context { + const test_context* oldCtx_; + const std::string msg_; + + public: + test_context(std::string ctx) : oldCtx_(results().context), msg_(std::move(ctx)) { results().context = this; } + ~test_context() { results().context = oldCtx_; } + friend std::ostream& operator<<(std::ostream& os, const test_context& c) + { + const test_context* current = &c; + os << "CONTEXT: "; + std::string indent = "\n\t"; + do { + os << indent << current->msg_; + indent += '\t'; + } while((current = current->oldCtx_) != nullptr); + return os; + } + }; + inline void report_error(const char* expr, const char* file, int line) { std::cerr << "Error at " << file << '#' << line << ": " << expr << std::endl; + const auto* context = results().context; + if(context) + std::cerr << ' ' << *context << std::endl; if(++boost::locale::test::results().error_counter > BOOST_LOCALE_ERROR_LIMIT) throw std::runtime_error("Error limits reached, stopping unit test"); } @@ -97,6 +124,10 @@ namespace boost { namespace locale { namespace test { BOOST_LOCALE_START_CONST_CONDITION \ } while(0) BOOST_LOCALE_END_CONST_CONDITION +#define TEST_CONTEXT(expr) \ + boost::locale::test::test_context BOOST_JOIN(test_context_, __COUNTER__)( \ + static_cast(std::stringstream{} << expr).str()) + void test_main(int argc, char** argv); int main(int argc, char** argv) diff --git a/test/test_catalog.cpp b/test/test_catalog.cpp index 6b884b99..bc06805f 100644 --- a/test/test_catalog.cpp +++ b/test/test_catalog.cpp @@ -31,10 +31,8 @@ void test_plural_expr_rand(const T& ref, const char* expr) const auto n = getRandValue(minVal, maxVal); const auto result = ptr(n); const auto refResult = ref(n); - if(result != refResult) { - std::cerr << "Expression: " << expr << "; n=" << n << '\n'; // LCOV_EXCL_LINE - TEST_EQ(result, refResult); // LCOV_EXCL_LINE - } + TEST_CONTEXT("Expression: " << expr << "; n=" << n); + TEST_EQ(result, refResult); } } diff --git a/test/test_codecvt.cpp b/test/test_codecvt.cpp index 7df704d3..504da439 100644 --- a/test/test_codecvt.cpp +++ b/test/test_codecvt.cpp @@ -53,10 +53,8 @@ void test_codecvt_in_n_m(const cvt_type& cvt, int n, int m) std::mbstate_t mb2 = mb; std::codecvt_base::result r = cvt.in(mb, from, end, from_next, to, to_end, to_next); - int count = cvt.length(mb2, from, end, to_end - to); + const int count = cvt.length(mb2, from, end, to_end - to); TEST_EQ(memcmp(&mb, &mb2, sizeof(mb)), 0); - if(count != from_next - from) - std::cout << count << " " << from_next - from << std::endl; // LCOV_EXCL_LINE TEST_EQ(count, from_next - from); if(r == cvt_type::partial) { diff --git a/test/test_encoding.cpp b/test/test_encoding.cpp index 4de7e610..7fb07c2f 100644 --- a/test/test_encoding.cpp +++ b/test/test_encoding.cpp @@ -832,10 +832,9 @@ void test_simple_encodings() const auto encodings = get_simple_encodings(); for(auto it = encodings.begin(), end = encodings.end(); it != end; ++it) { TEST_EQ(normalize_encoding(*it), *it); // Must be normalized - const auto it2 = std::find(it + 1, end, *it); - TEST(it2 == end); - if(it2 != end) - std::cerr << "Duplicate entry: " << *it << '\n'; // LCOV_EXCL_LINE + TEST_CONTEXT("Entry: " << *it); + // Must be unique + TEST(std::find(it + 1, end, *it) == end); } const auto it = std::is_sorted_until(encodings.begin(), encodings.end()); TEST(it == encodings.end()); @@ -852,10 +851,9 @@ void test_win_codepages() auto is_same_win_codepage = [&it](const windows_encoding& rhs) -> bool { return it->codepage == rhs.codepage && std::strcmp(it->name, rhs.name) == 0; }; - const auto* it2 = std::find_if(it + 1, end, is_same_win_codepage); - TEST(it2 == end); - if(it2 != end) - std::cerr << "Duplicate entry: " << it->name << ':' << it->codepage << '\n'; // LCOV_EXCL_LINE + TEST_CONTEXT("Entry: " << it->name << ':' << it->codepage); + // Must be unique + TEST(std::find_if(it + 1, end, is_same_win_codepage) == end); } const auto cmp = [](const windows_encoding& rhs, const windows_encoding& lhs) -> bool { return rhs < lhs.name; }; const auto* it = std::is_sorted_until(all_windows_encodings, std::end(all_windows_encodings), cmp); diff --git a/test/test_formatting.cpp b/test/test_formatting.cpp index e2df2adc..41c30a0f 100644 --- a/test/test_formatting.cpp +++ b/test/test_formatting.cpp @@ -300,6 +300,7 @@ void test_parse_fail_impl(std::basic_istringstream& ss, int line) #define TEST_MIN_MAX_POSIX(type) \ do { \ + TEST_CONTEXT(#type); \ const std::string minval = as_posix_string(std::numeric_limits::min()); \ const std::string maxval = as_posix_string(std::numeric_limits::max()); \ TEST_MIN_MAX_FMT(as::posix, type, minval, maxval); \ @@ -340,6 +341,7 @@ void test_as_posix(const std::string& e_charset = "UTF-8") localization_backend_manager::global(backend); for(const std::string name : {"en_US", "ru_RU", "de_DE"}) { const std::locale loc = boost::locale::generator{}(name + "." + e_charset); + TEST_CONTEXT("Locale " << (name + "." + e_charset)); TEST_MIN_MAX_POSIX(int16_t); TEST_MIN_MAX_POSIX(uint16_t);