1 // Include loguru first to test that it needs no dependencies:
2 #define LOGURU_FILENAME_WIDTH  16
3 #define LOGURU_WITH_STREAMS     1
4 #define LOGURU_REDEFINE_ASSERT  1
5 #define LOGURU_USE_FMTLIB       0
6 #define LOGURU_WITH_FILEABS     0
7 #define LOGURU_IMPLEMENTATION   1
8 // #define LOGURU_STACKTRACES      1
9 // #define LOGURU_RTTI             1
10 #include "../loguru.hpp"
11 
12 #include <chrono>
13 #include <string>
14 #include <thread>
15 
16 #include <fstream>
17 
the_one_where_the_problem_is(const std::vector<std::string> & v)18 void the_one_where_the_problem_is(const std::vector<std::string>& v) {
19 	ABORT_F("Abort deep in stack trace, msg: %s", v[0].c_str());
20 }
deep_abort_1(const std::vector<std::string> & v)21 void deep_abort_1(const std::vector<std::string>& v) { the_one_where_the_problem_is(v); }
deep_abort_2(const std::vector<std::string> & v)22 void deep_abort_2(const std::vector<std::string>& v) { deep_abort_1(v); }
deep_abort_3(const std::vector<std::string> & v)23 void deep_abort_3(const std::vector<std::string>& v) { deep_abort_2(v); }
deep_abort_4(const std::vector<std::string> & v)24 void deep_abort_4(const std::vector<std::string>& v) { deep_abort_3(v); }
deep_abort_5(const std::vector<std::string> & v)25 void deep_abort_5(const std::vector<std::string>& v) { deep_abort_4(v); }
deep_abort_6(const std::vector<std::string> & v)26 void deep_abort_6(const std::vector<std::string>& v) { deep_abort_5(v); }
deep_abort_7(const std::vector<std::string> & v)27 void deep_abort_7(const std::vector<std::string>& v) { deep_abort_6(v); }
deep_abort_8(const std::vector<std::string> & v)28 void deep_abort_8(const std::vector<std::string>& v) { deep_abort_7(v); }
deep_abort_9(const std::vector<std::string> & v)29 void deep_abort_9(const std::vector<std::string>& v) { deep_abort_8(v); }
deep_abort_10(const std::vector<std::string> & v)30 void deep_abort_10(const std::vector<std::string>& v) { deep_abort_9(v); }
31 
sleep_ms(int ms)32 void sleep_ms(int ms)
33 {
34 	LOG_F(3, "Sleeping for %d ms", ms);
35 	std::this_thread::sleep_for(std::chrono::milliseconds(ms));
36 }
37 
test_thread_names()38 void test_thread_names()
39 {
40 	LOG_SCOPE_FUNCTION(INFO);
41 
42 	{
43 		char thread_name[17];
44 		loguru::get_thread_name(thread_name, sizeof(thread_name), false);
45 		LOG_F(INFO, "Hello from main thread ('%s')", thread_name);
46 	}
47 
48 	auto a = std::thread([](){
49 		char thread_name[17];
50 		loguru::get_thread_name(thread_name, sizeof(thread_name), false);
51 		LOG_F(INFO, "Hello from nameless thread ('%s')", thread_name);
52 	});
53 
54 	auto b = std::thread([](){
55 		loguru::set_thread_name("renderer");
56 		char thread_name[17];
57 		loguru::get_thread_name(thread_name, sizeof(thread_name), false);
58 		LOG_F(INFO, "Hello from render thread ('%s')", thread_name);
59 	});
60 
61 	auto c = std::thread([](){
62 		loguru::set_thread_name("abcdefghijklmnopqrstuvwxyz");
63 		char thread_name[17];
64 		loguru::get_thread_name(thread_name, sizeof(thread_name), false);
65 		LOG_F(INFO, "Hello from thread with a very long name ('%s')", thread_name);
66 	});
67 
68 	a.join();
69 	b.join();
70 	c.join();
71 }
72 
test_scopes()73 void test_scopes()
74 {
75 	LOG_SCOPE_FUNCTION(INFO);
76 
77 	LOG_F(INFO, "Should be indented one step");
78 	LOG_F(1, "First thing");
79 	LOG_F(1, "Second thing");
80 
81 	{
82 		LOG_SCOPE_F(1, "Some indentation at level 1");
83 		LOG_F(INFO, "Should only be indented one more step iff verbosity is 1 or higher");
84 		LOG_F(2, "Some info");
85 		sleep_ms(123);
86 	}
87 
88 	sleep_ms(64);
89 }
90 
test_levels()91 void test_levels()
92 {
93 	LOG_SCOPE_FUNCTION(INFO);
94 	{
95 		VLOG_SCOPE_F(1, "Scope with verbosity 1");
96 		LOG_F(3,       "Only visible with -v 3 or higher");
97 		LOG_F(2,       "Only visible with -v 2 or higher");
98 		LOG_F(1,       "Only visible with -v 1 or higher");
99 	}
100 	LOG_F(0,       "LOG_F(0)");
101 	LOG_F(INFO,    "This is some INFO");
102 	LOG_F(WARNING, "This is a WARNING");
103 	LOG_F(ERROR,   "This is a serious ERROR");
104 }
105 
106 #if LOGURU_WITH_STREAMS
test_stream()107 void test_stream()
108 {
109 	LOG_SCOPE_FUNCTION(INFO);
110 	LOG_S(INFO) << "Testing stream-logging.";
111 	LOG_S(INFO) << "First line" << std::endl << "Seconds line.";
112 	LOG_S(1) << "Stream-logging with verbosity 1";
113 	LOG_S(2) << "Stream-logging with verbosity 2";
114 	LOG_S(3) << "Stream-logging with verbosity 3";
115 	LOG_IF_S(INFO, true) << "Should be visible";
116 	LOG_IF_S(INFO, false) << "SHOULD NOT BE VISIBLE";
117 	LOG_IF_S(1, true) << "Should be visible if verbosity is at least 1";
118 	LOG_IF_S(1, false) << "SHOULD NOT BE VISIBLE";
119 	CHECK_LT_S(1, 2);
120 	CHECK_GT_S(3, 2) << "Weird";
121 }
122 #endif
123 
some_expensive_operation()124 int some_expensive_operation() { static int r=31; sleep_ms(132); return r++; }
125 const int BAD = 32;
126 
always_increasing()127 int always_increasing() { static int x = 0; return x++; }
128 
main_test(int argc,char * argv[])129 int main_test(int argc, char* argv[])
130 {
131 	loguru::init(argc, argv);
132 	LOG_SCOPE_FUNCTION(INFO);
133 	LOG_F(INFO, "Doing some stuff...");
134 	for (int i=0; i<2; ++i) {
135 		LOG_SCOPE_F(1, "Iteration %d", i);
136 		auto result = some_expensive_operation();
137 		LOG_IF_F(WARNING, result == BAD, "Bad result");
138 	}
139 	LOG_F(INFO, "Time to go!");
140 
141 	return 0;
142 }
143 
test_SIGSEGV_0()144 void test_SIGSEGV_0()
145 {
146 	LOG_F(INFO, "Intentionally writing to nullptr:");
147 	int* ptr = nullptr;
148 	*ptr = 42;
149 	LOG_F(FATAL, "We shouldn't get here");
150 }
test_SIGSEGV_1()151 void test_SIGSEGV_1() { test_SIGSEGV_0(); }
test_SIGSEGV_2()152 void test_SIGSEGV_2() { test_SIGSEGV_1(); }
153 
test_abort_0()154 void test_abort_0()
155 {
156 	LOG_F(INFO, "Calling std::abort");
157 	std::abort();
158 }
test_abort_1()159 void test_abort_1() { test_abort_0(); }
test_abort_2()160 void test_abort_2() { test_abort_1(); }
161 
162 struct CustomType
163 {
164 	std::string contents;
165 };
166 
167 namespace loguru {
ec_to_text(const CustomType * custom)168 Text ec_to_text(const CustomType* custom)
169 {
170 	return Text(strdup(custom->contents.c_str()));
171 }
172 } // namespace loguru
173 
test_error_contex()174 void test_error_contex()
175 {
176 	{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
177 	ERROR_CONTEXT("Parent thread value", 42);
178 	{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
179 	char parent_thread_name[17];
180 	loguru::get_thread_name(parent_thread_name, sizeof(parent_thread_name), false);
181 	ERROR_CONTEXT("Parent thread name", &parent_thread_name[0]);
182 
183 	const auto parent_ec_handle = loguru::get_thread_ec_handle();
184 
185 	std::thread([=]{
186 		loguru::set_thread_name("EC test thread");
187 		ERROR_CONTEXT("parent error context", parent_ec_handle);
188 		{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
189 		ERROR_CONTEXT("const char*",       "test string");
190 		ERROR_CONTEXT("integer",           42);
191 		ERROR_CONTEXT("float",              3.14f);
192 		ERROR_CONTEXT("double",             3.14);
193 		{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
194 		ERROR_CONTEXT("char A",            'A');
195 		ERROR_CONTEXT("char backslash",    '\\');
196 		ERROR_CONTEXT("char double-quote", '\"');
197 		ERROR_CONTEXT("char single-quote", '\'');
198 		ERROR_CONTEXT("char zero",         '\0');
199 		ERROR_CONTEXT("char bell",         '\b');
200 		ERROR_CONTEXT("char feed",         '\f');
201 		ERROR_CONTEXT("char newline",      '\n');
202 		ERROR_CONTEXT("char return",       '\r');
203 		ERROR_CONTEXT("char tab",          '\t');
204 		ERROR_CONTEXT("char x13",          '\u0013');
205 		{ ERROR_CONTEXT("THIS SHOULDN'T BE PRINTED", "scoped"); }
206 		CustomType custom{"custom_contents"};
207 		ERROR_CONTEXT("CustomType", &custom);
208 		ABORT_F("Intentional abort");
209 	}).join();
210 }
211 
test_hang_0()212 void test_hang_0()
213 {
214 	LOG_F(INFO, "Press ctrl-C to kill.");
215 	for(;;) {
216 		// LOG_F(INFO, "Press ctrl-C to break out of this infinite loop.");
217 	}
218 }
test_hang_1()219 void test_hang_1() { test_hang_0(); }
test_hang_2()220 void test_hang_2() { test_hang_1(); }
221 
throw_on_fatal()222 void throw_on_fatal()
223 {
224 	loguru::set_fatal_handler([](const loguru::Message& message){
225 		LOG_F(INFO, "Throwing exception...");
226 		throw std::runtime_error(std::string(message.prefix) + message.message);
227 	});
228 	{
229 		LOG_SCOPE_F(INFO, "CHECK_F throw + catch");
230 		try {
231 			CHECK_F(false, "some CHECK_F message");
232 		} catch (std::runtime_error& e) {
233 			LOG_F(INFO, "CHECK_F threw this: '%s'", e.what());
234 		}
235 	}
236 #if LOGURU_WITH_STREAMS
237 	{
238 		LOG_SCOPE_F(INFO, "CHECK_S throw + catch");
239 		try {
240 			CHECK_S(false) << "Some CHECK_S message";
241 		} catch (std::runtime_error& e) {
242 			LOG_F(INFO, "CHECK_S threw this: '%s'", e.what());
243 		}
244 	}
245 	LOG_F(INFO, "Trying an uncaught exception:");
246 	CHECK_S(false);
247 #else
248 	CHECK_F(false);
249 #endif // LOGURU_WITH_STREAMS
250 }
251 
throw_on_signal()252 void throw_on_signal()
253 {
254 	loguru::set_fatal_handler([](const loguru::Message& message){
255 		LOG_F(INFO, "Throwing exception...");
256 		throw std::runtime_error(std::string(message.prefix) + message.message);
257 	});
258 	test_SIGSEGV_0();
259 }
260 
261 // void die(std::ofstream& of)
262 // {
263 // 	(void)of;
264 // 	test_hang_2();
265 // }
266 
267 // ----------------------------------------------------------------------------
268 
269 struct CallbackTester
270 {
271 	size_t num_print = 0;
272 	size_t num_flush = 0;
273 	size_t num_close = 0;
274 };
275 
callbackPrint(void * user_data,const loguru::Message & message)276 void callbackPrint(void* user_data, const loguru::Message& message)
277 {
278     printf("Custom callback: %s%s\n", message.prefix, message.message);
279     reinterpret_cast<CallbackTester*>(user_data)->num_print += 1;
280 }
281 
callbackFlush(void * user_data)282 void callbackFlush(void* user_data)
283 {
284 	printf("Custom callback flush\n");
285     reinterpret_cast<CallbackTester*>(user_data)->num_flush += 1;
286 }
287 
callbackClose(void * user_data)288 void callbackClose(void* user_data)
289 {
290 	printf("Custom callback close\n");
291     reinterpret_cast<CallbackTester*>(user_data)->num_close += 1;
292 }
293 
test_log_callback()294 void test_log_callback()
295 {
296 	CallbackTester tester;
297 	loguru::add_callback(
298 		"user_callback", callbackPrint, &tester,
299 		loguru::Verbosity_INFO, callbackClose, callbackFlush);
300 	CHECK_EQ_F(tester.num_print, 0u);
301 	LOG_F(INFO, "Test print");
302 	CHECK_EQ_F(tester.num_print, 1u);
303 	CHECK_EQ_F(tester.num_close, 0u);
304 	CHECK_EQ_F(tester.num_flush, 1u);
305 	loguru::flush();
306 	CHECK_EQ_F(tester.num_flush, 2u);
307 	loguru::remove_callback("user_callback");
308 	CHECK_EQ_F(tester.num_close, 1u);
309 }
310 
311 #if defined _WIN32 && defined _DEBUG
312 #define USE_WIN_DBG_HOOK
winDbgHook(int reportType,char * message,int *)313 static int winDbgHook(int reportType, char *message, int *)
314 {
315 	fprintf(stderr, "Report type: %d\nMessage: %s\n", reportType,
316 	                (nullptr != message ? message : "nullptr message"));
317     return 1; // To prevent the Abort, Retry, Ignore dialog
318 }
319 #endif
320 
321 // ----------------------------------------------------------------------------
322 
main(int argc,char * argv[])323 int main(int argc, char* argv[])
324 {
325 #ifdef USE_WIN_DBG_HOOK
326 	_CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, winDbgHook);
327 #endif
328 
329 	// loguru::g_preamble = false;
330 
331 	if (argc > 1 && argv[1] == std::string("test"))
332 	{
333 		return main_test(argc, argv);
334 	}
335 
336 	loguru::init(argc, argv);
337 
338 	// auto verbose_type_name = loguru::demangle(typeid(std::ofstream).name());
339 	// loguru::add_stack_cleanup(verbose_type_name.c_str(), "std::ofstream");
340 	// std::ofstream os;
341 	// die(os);
342 
343 	if (argc == 1)
344 	{
345 		loguru::add_file("latest_readable.log", loguru::Truncate, loguru::Verbosity_INFO);
346 		loguru::add_file("everything.log",      loguru::Append,   loguru::Verbosity_MAX);
347 
348 		LOG_F(INFO, "Loguru test");
349 		test_thread_names();
350 
351 		test_scopes();
352 		test_levels();
353 		#if LOGURU_WITH_STREAMS
354 		test_stream();
355 		#endif
356 
357 		loguru::shutdown();
358 
359 		LOG_F(INFO, "goes to stderr, but not to file");
360 	}
361 	else
362 	{
363 		std::string test = argv[1];
364 		if (test == "ABORT_F") {
365 			ABORT_F("ABORT_F format message");
366 		} else if (test == "ABORT_S") {
367 			ABORT_S() << "ABORT_S stream message";
368 		} else if (test == "assert") {
369 			const char* ptr = 0;
370 			assert(ptr && "Error that was unexpected");
371 		} else if (test == "LOG_F_FATAL") {
372 			LOG_F(FATAL, "Fatal format message");
373 		} else if (test == "LOG_S_FATAL") {
374 			LOG_S(FATAL) << "Fatal stream message";
375 		} else if (test == "CHECK_NOTNULL_F") {
376 			const char* ptr = 0;
377 			CHECK_NOTNULL_F(ptr);
378 		} else if (test == "CHECK_F") {
379 			CHECK_F(1 > 2);
380 		} else if (test == "CHECK_EQ_F") {
381 			CHECK_EQ_F(always_increasing(),  0);
382 			CHECK_EQ_F(always_increasing(),  1);
383 			CHECK_EQ_F(always_increasing(), 42);
384 		} else if (test == "CHECK_EQ_F_int") {
385 			int x = 42;
386 			CHECK_EQ_F(x, x + 1);
387 		} else if (test == "CHECK_EQ_F_unsigned") {
388 			unsigned x = 42;
389 			CHECK_EQ_F(x, x + 1);
390 		} else if (test == "CHECK_EQ_F_size_t") {
391 			size_t x = 42;
392 			CHECK_EQ_F(x, x + 1);
393 		} else if (test == "CHECK_EQ_F_message") {
394 			CHECK_EQ_F(always_increasing(),  0, "Should pass");
395 			CHECK_EQ_F(always_increasing(),  1, "Should pass");
396 			CHECK_EQ_F(always_increasing(), 42, "Should fail");
397 		} else if (test == "CHECK_EQ_S") {
398 			std::string str = "right";
399 			CHECK_EQ_F(str, "wrong", "Expected to fail, since `str` isn't \"wrong\" but \"%s\"", str.c_str());
400 		} else if (test == "CHECK_LT_S") {
401 			CHECK_EQ_F(always_increasing(), 0);
402 			CHECK_EQ_F(always_increasing(), 1);
403 			CHECK_EQ_F(always_increasing(), 42);
404 		} else if (test == "CHECK_LT_S_message") {
405 			CHECK_EQ_F(always_increasing(),  0, "Should pass");
406 			CHECK_EQ_F(always_increasing(),  1, "Should pass");
407 			CHECK_EQ_F(always_increasing(), 42, "Should fail!");
408 		} else if (test == "deep_abort") {
409 			deep_abort_10({"deep_abort"});
410 		} else if (test == "SIGSEGV") {
411 			test_SIGSEGV_2();
412 		} else if (test == "abort") {
413 			test_abort_2();
414 		} else if (test == "error_context") {
415 			test_error_contex();
416 		} else if (test == "throw_on_fatal") {
417 			throw_on_fatal();
418 		} else if (test == "throw_on_signal") {
419 			throw_on_signal();
420 		} else if (test == "callback") {
421 			test_log_callback();
422 		} else if (test == "hang") {
423 			loguru::add_file("hang.log", loguru::Truncate, loguru::Verbosity_INFO);
424 			test_hang_2();
425 		} else {
426 			LOG_F(ERROR, "Unknown test: '%s'", test.c_str());
427 		}
428 	}
429 }
430