1 | // |
2 | // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) |
3 | // Copyright (c) 2021-2022 Alexander Grund |
4 | // Copyright (c) 2002, 2009, 2014 Peter Dimov |
5 | // |
6 | // Distributed under the Boost Software License, Version 1.0. |
7 | // https://www.boost.org/LICENSE_1_0.txt |
8 | |
9 | #ifndef BOOST_LOCALE_UNIT_TEST_HPP |
10 | #define BOOST_LOCALE_UNIT_TEST_HPP |
11 | |
12 | #include <boost/locale/config.hpp> |
13 | #include <cstdlib> |
14 | #include <iomanip> |
15 | #include <iostream> |
16 | #include <sstream> |
17 | #include <stdexcept> |
18 | #include <string> |
19 | #include <typeinfo> |
20 | #include <vector> |
21 | |
22 | #if defined(_MSC_VER) && defined(_CPPLIB_VER) && defined(_DEBUG) |
23 | # include <crtdbg.h> |
24 | #endif |
25 | |
26 | namespace boost { namespace locale { namespace test { |
27 | /// Name/path of current executable |
28 | std::string exe_name; |
29 | |
30 | struct test_result { |
31 | test_result() : error_counter(0), test_counter(0) |
32 | { |
33 | #if defined(_MSC_VER) && (_MSC_VER > 1310) |
34 | // disable message boxes on assert(), abort() |
35 | ::_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); |
36 | #endif |
37 | #if defined(_MSC_VER) && defined(_CPPLIB_VER) && defined(_DEBUG) |
38 | // disable message boxes on iterator debugging violations |
39 | _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); |
40 | _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); |
41 | #endif |
42 | } |
43 | int error_counter; |
44 | int test_counter; |
45 | }; |
46 | inline test_result& results() |
47 | { |
48 | static test_result instance; |
49 | return instance; |
50 | } |
51 | }}} // namespace boost::locale::test |
52 | |
53 | #ifndef BOOST_LOCALE_ERROR_LIMIT |
54 | # define BOOST_LOCALE_ERROR_LIMIT 20 |
55 | #endif |
56 | |
57 | #define BOOST_LOCALE_STRINGIZE(x) #x |
58 | |
59 | #define THROW_IF_TOO_BIG(X) \ |
60 | if((X) > BOOST_LOCALE_ERROR_LIMIT) \ |
61 | throw std::runtime_error("Error limits reached, stopping unit test") |
62 | |
63 | #define TEST(X) \ |
64 | do { \ |
65 | boost::locale::test::results().test_counter++; \ |
66 | if(X) \ |
67 | break; \ |
68 | std::cerr << "Error in line:" << __LINE__ << " " #X << std::endl; \ |
69 | THROW_IF_TOO_BIG(boost::locale::test::results().error_counter++); \ |
70 | BOOST_LOCALE_START_CONST_CONDITION \ |
71 | } while(0) BOOST_LOCALE_END_CONST_CONDITION |
72 | |
73 | #define TEST_REQUIRE(X) \ |
74 | do { \ |
75 | boost::locale::test::results().test_counter++; \ |
76 | if(X) \ |
77 | break; \ |
78 | std::cerr << "Error in line " << __LINE__ << ": " #X << std::endl; \ |
79 | throw std::runtime_error("Critical test " #X " failed"); \ |
80 | BOOST_LOCALE_START_CONST_CONDITION \ |
81 | } while(0) BOOST_LOCALE_END_CONST_CONDITION |
82 | |
83 | #define TEST_THROWS(X, E) \ |
84 | do { \ |
85 | boost::locale::test::results().test_counter++; \ |
86 | try { \ |
87 | X; \ |
88 | } catch(E const& /*e*/) { \ |
89 | break; \ |
90 | } catch(...) { \ |
91 | } \ |
92 | std::cerr << "Error in line " << __LINE__ << ": " #X << std::endl; \ |
93 | THROW_IF_TOO_BIG(boost::locale::test::results().error_counter++); \ |
94 | BOOST_LOCALE_START_CONST_CONDITION \ |
95 | } while(0) BOOST_LOCALE_END_CONST_CONDITION |
96 | |
97 | void test_main(int argc, char** argv); |
98 | |
99 | int main(int argc, char** argv) |
100 | { |
101 | { |
102 | using namespace boost::locale::test; |
103 | exe_name = argv[0]; |
104 | if(exe_name.substr(pos: exe_name.length() - 4) == ".exe" ) |
105 | exe_name.resize(n: exe_name.length() - 4); |
106 | results(); // Instantiate |
107 | } |
108 | try { |
109 | test_main(argc, argv); |
110 | } catch(const std::exception& e) { |
111 | std::cerr << "Failed with exception " // LCOV_EXCL_LINE |
112 | << typeid(e).name() << '(' << e.what() << ')' << std::endl; // LCOV_EXCL_LINE |
113 | return EXIT_FAILURE; // LCOV_EXCL_LINE |
114 | } |
115 | using boost::locale::test::results; |
116 | if(results().test_counter > 0) { |
117 | int passed = results().test_counter - results().error_counter; |
118 | std::cout << std::endl; |
119 | std::cout << "Passed " << passed << " tests\n" ; |
120 | if(results().error_counter > 0) |
121 | std::cout << "Failed " << results().error_counter << " tests\n" ; // LCOV_EXCL_LINE |
122 | std::cout << " " << std::fixed << std::setprecision(1) << std::setw(5) |
123 | << 100.0 * passed / results().test_counter << "% of tests completed successfully\n" ; |
124 | } |
125 | return results().error_counter == 0 ? EXIT_SUCCESS : EXIT_FAILURE; |
126 | } |
127 | |
128 | template<typename T> |
129 | std::string to_string(T const& s) |
130 | { |
131 | std::stringstream ss; |
132 | ss << s; |
133 | return ss.str(); |
134 | } |
135 | |
136 | const std::string& to_string(const std::string& s) |
137 | { |
138 | return s; |
139 | } |
140 | |
141 | template<typename T> |
142 | std::string to_string(const std::vector<T>& v) |
143 | { |
144 | std::stringstream ss; |
145 | bool first = true; |
146 | for(const T& e : v) { |
147 | if(!first) |
148 | ss << ", " ; |
149 | first = false; |
150 | ss << to_string(e); |
151 | } |
152 | return ss.str(); |
153 | } |
154 | |
155 | /// Put the char into the stream making sure it is readable |
156 | /// Fallback to the Unicode representation of it (e.g. U+00A0) |
157 | template<typename Char> |
158 | void stream_char(std::ostream& s, const Char c) |
159 | { |
160 | if((c >= '!' && c <= '~') || c == ' ') |
161 | s << static_cast<char>(c); |
162 | else |
163 | s << "U+" << std::hex << std::uppercase << std::setw(sizeof(Char)) << std::setfill('0') |
164 | << static_cast<unsigned>(c); |
165 | } |
166 | |
167 | template<typename Char> |
168 | std::string to_string(const std::basic_string<Char>& s) |
169 | { |
170 | std::stringstream ss; |
171 | for(const Char c : s) |
172 | stream_char(ss, c); |
173 | return ss.str(); |
174 | } |
175 | |
176 | template<size_t size> |
177 | std::string to_string(const wchar_t (&s)[size]) |
178 | { |
179 | std::stringstream ss; |
180 | for(size_t i = 0; i < size; ++i) |
181 | stream_char(ss, s[i]); |
182 | return ss.str(); |
183 | } |
184 | |
185 | std::string to_string(const wchar_t* s) |
186 | { |
187 | std::stringstream ss; |
188 | for(; *s; ++s) |
189 | stream_char(s&: ss, c: *s); |
190 | return ss.str(); |
191 | } |
192 | |
193 | // Unicode chars cannot be streamed directly (deleted overloads in C++20) |
194 | template<typename Char> |
195 | std::string to_string_char_impl(const Char c) |
196 | { |
197 | std::stringstream ss; |
198 | stream_char(ss, c); |
199 | return ss.str(); |
200 | } |
201 | |
202 | std::string to_string(const wchar_t c) |
203 | { |
204 | return to_string_char_impl(c); |
205 | } |
206 | std::string to_string(const char16_t c) |
207 | { |
208 | return to_string_char_impl(c); |
209 | } |
210 | std::string to_string(const char32_t c) |
211 | { |
212 | return to_string_char_impl(c); |
213 | } |
214 | |
215 | #if BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 120000) && BOOST_WORKAROUND(__cplusplus, >= 202002L) |
216 | // Avoid warning due to comparison-to-spaceship-rewrite, happening e.g. for string comparison |
217 | // see https://github.com/llvm/llvm-project/issues/43670 |
218 | # define BOOST_LOCALE_SPACESHIP_NULLPTR_WARNING 1 |
219 | # pragma clang diagnostic push |
220 | # pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" |
221 | #else |
222 | # define BOOST_LOCALE_SPACESHIP_NULLPTR_WARNING 0 |
223 | #endif |
224 | |
225 | template<typename T, typename U> |
226 | void test_impl(bool success, T const& l, U const& r, const char* expr, const char* fail_expr, int line) |
227 | { |
228 | boost::locale::test::results().test_counter++; |
229 | if(!success) { |
230 | std::cerr << "Error in line " << line << ": " << expr << std::endl; |
231 | std::cerr << "---- [" << to_string(l) << "] " << fail_expr << " [" << to_string(r) << "]" << std::endl; |
232 | THROW_IF_TOO_BIG(boost::locale::test::results().error_counter++); |
233 | } |
234 | } |
235 | |
236 | template<typename T, typename U> |
237 | void test_eq_impl(T const& l, U const& r, const char* expr, int line) |
238 | { |
239 | test_impl(l == r, l, r, expr, "!=" , line); |
240 | } |
241 | |
242 | template<typename T, typename U> |
243 | void test_ne_impl(T const& l, U const& r, const char* expr, int line) |
244 | { |
245 | test_impl(l != r, l, r, expr, "==" , line); |
246 | } |
247 | |
248 | template<typename T, typename U> |
249 | void test_le_impl(T const& l, U const& r, const char* expr, int line) |
250 | { |
251 | test_impl(l <= r, l, r, expr, ">" , line); |
252 | } |
253 | |
254 | template<typename T, typename U> |
255 | void test_lt_impl(T const& l, U const& r, const char* expr, int line) |
256 | { |
257 | test_impl(l < r, l, r, expr, ">=" , line); |
258 | } |
259 | |
260 | template<typename T, typename U> |
261 | void test_ge_impl(T const& l, U const& r, const char* expr, int line) |
262 | { |
263 | test_impl(l >= r, l, r, expr, "<" , line); |
264 | } |
265 | |
266 | template<typename T, typename U> |
267 | void test_gt_impl(T const& l, U const& r, const char* expr, int line) |
268 | { |
269 | test_impl(l > r, l, r, expr, "<=" , line); |
270 | } |
271 | |
272 | #define TEST_EQ(x, y) test_eq_impl(x, y, BOOST_LOCALE_STRINGIZE(x == y), __LINE__) |
273 | #define TEST_NE(x, y) test_ne_impl(x, y, BOOST_LOCALE_STRINGIZE(x != y), __LINE__) |
274 | #define TEST_LE(x, y) test_le_impl(x, y, BOOST_LOCALE_STRINGIZE(x <= y), __LINE__) |
275 | #define TEST_LT(x, y) test_lt_impl(x, y, BOOST_LOCALE_STRINGIZE(x < y), __LINE__) |
276 | #define TEST_GE(x, y) test_ge_impl(x, y, BOOST_LOCALE_STRINGIZE(x >= y), __LINE__) |
277 | #define TEST_GT(x, y) test_gt_impl(x, y, BOOST_LOCALE_STRINGIZE(x > y), __LINE__) |
278 | |
279 | #if BOOST_LOCALE_SPACESHIP_NULLPTR_WARNING |
280 | # pragma clang diagnostic pop |
281 | #endif |
282 | |
283 | #ifdef BOOST_MSVC |
284 | # define BOOST_LOCALE_DISABLE_UNREACHABLE_CODE_WARNING __pragma(warning(disable : 4702)) |
285 | #else |
286 | # define BOOST_LOCALE_DISABLE_UNREACHABLE_CODE_WARNING |
287 | #endif |
288 | |
289 | #endif |
290 | |