1 /*
2 
3 Copyright (c) 2008, Arvid Norberg
4 All rights reserved.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 
10     * Redistributions of source code must retain the above copyright
11       notice, this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright
13       notice, this list of conditions and the following disclaimer in
14       the documentation and/or other materials provided with the distribution.
15     * Neither the name of the author nor the names of its
16       contributors may be used to endorse or promote products derived
17       from this software without specific prior written permission.
18 
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
30 
31 */
32 
33 #include <iostream>
34 #include <boost/config.hpp>
35 #include <fcntl.h>
36 #include <cstdio>
37 #include <cstdlib> // for exit()
38 #include "libtorrent/address.hpp"
39 #include "libtorrent/socket.hpp"
40 #include "setup_transfer.hpp" // for _g_test_failures
41 #include "test.hpp"
42 #include "dht_server.hpp" // for stop_dht
43 #include "peer_server.hpp" // for stop_peer
44 #include "udp_tracker.hpp" // for stop_udp_tracker
45 #include <boost/system/system_error.hpp>
46 
47 #include "libtorrent/assert.hpp"
48 #include "libtorrent/aux_/path.hpp"
49 #include "libtorrent/random.hpp"
50 #include "libtorrent/aux_/escape_string.hpp"
51 #include <csignal>
52 
53 #ifdef _WIN32
54 #include "libtorrent/aux_/windows.hpp" // fot SetErrorMode
55 #include <io.h> // for _dup and _dup2
56 #include <process.h> // for _getpid
57 #include <crtdbg.h>
58 
59 #define dup _dup
60 #define dup2 _dup2
61 
62 #else
63 
64 #include <unistd.h> // for getpid()
65 
66 #endif
67 
68 using namespace lt;
69 
70 namespace {
71 
72 // these are global so we can restore them on abnormal exits and print stuff
73 // out, such as the log
74 int old_stdout = -1;
75 int old_stderr = -1;
76 bool redirect_stdout = true;
77 // sanitizer output will go to stderr and we won't get an opportunity to print
78 // it, so don't redirect stderr by default
79 bool redirect_stderr = false;
80 bool keep_files = false;
81 
82 // the current tests file descriptor
83 unit_test_t* current_test = nullptr;
84 
output_test_log_to_terminal()85 void output_test_log_to_terminal()
86 {
87 	if (current_test == nullptr
88 		|| current_test->output == nullptr)
89 		return;
90 
91 	fflush(stdout);
92 	fflush(stderr);
93 	if (old_stdout != -1)
94 	{
95 		dup2(old_stdout, fileno(stdout));
96 		old_stdout = -1;
97 	}
98 	if (old_stderr != -1)
99 	{
100 		dup2(old_stderr, fileno(stderr));
101 		old_stderr = -1;
102 	}
103 
104 	fseek(current_test->output, 0, SEEK_SET);
105 	std::printf("\x1b[1m[%s]\x1b[0m\n\n", current_test->name);
106 	char buf[4096];
107 	std::size_t size = 0;
108 	do {
109 		size = fread(buf, 1, sizeof(buf), current_test->output);
110 		if (size > 0) fwrite(buf, 1, size, stdout);
111 	} while (size > 0);
112 }
113 
114 #ifdef _WIN32
seh_exception_handler(LPEXCEPTION_POINTERS p)115 LONG WINAPI seh_exception_handler(LPEXCEPTION_POINTERS p)
116 {
117 	char stack_text[10000];
118 
119 #if TORRENT_USE_ASSERTS \
120 	|| defined TORRENT_ASIO_DEBUGGING \
121 	|| defined TORRENT_PROFILE_CALLS \
122 	|| defined TORRENT_DEBUG_BUFFERS
123 	print_backtrace(stack_text, sizeof(stack_text), 30
124 		, p->ContextRecord);
125 #elif defined __FUNCTION__
126 	strcpy(stack_text, __FUNCTION__);
127 #else
128 	strcpy(stack_text, "<stack traces disabled>");
129 #endif
130 
131 	int const code = p->ExceptionRecord->ExceptionCode;
132 	char const* name = "<unknown exception>";
133 	switch (code)
134 	{
135 #define EXC(x) case x: name = #x; break
136 		EXC(EXCEPTION_ACCESS_VIOLATION);
137 		EXC(EXCEPTION_ARRAY_BOUNDS_EXCEEDED);
138 		EXC(EXCEPTION_BREAKPOINT);
139 		EXC(EXCEPTION_DATATYPE_MISALIGNMENT);
140 		EXC(EXCEPTION_FLT_DENORMAL_OPERAND);
141 		EXC(EXCEPTION_FLT_DIVIDE_BY_ZERO);
142 		EXC(EXCEPTION_FLT_INEXACT_RESULT);
143 		EXC(EXCEPTION_FLT_INVALID_OPERATION);
144 		EXC(EXCEPTION_FLT_OVERFLOW);
145 		EXC(EXCEPTION_FLT_STACK_CHECK);
146 		EXC(EXCEPTION_FLT_UNDERFLOW);
147 		EXC(EXCEPTION_ILLEGAL_INSTRUCTION);
148 		EXC(EXCEPTION_IN_PAGE_ERROR);
149 		EXC(EXCEPTION_INT_DIVIDE_BY_ZERO);
150 		EXC(EXCEPTION_INT_OVERFLOW);
151 		EXC(EXCEPTION_INVALID_DISPOSITION);
152 		EXC(EXCEPTION_NONCONTINUABLE_EXCEPTION);
153 		EXC(EXCEPTION_PRIV_INSTRUCTION);
154 		EXC(EXCEPTION_SINGLE_STEP);
155 		EXC(EXCEPTION_STACK_OVERFLOW);
156 #undef EXC
157 	};
158 
159 	std::printf("exception: (0x%x) %s caught:\n%s\n"
160 		, code, name, stack_text);
161 
162 	output_test_log_to_terminal();
163 
164 	exit(code);
165 }
166 
167 #endif
168 
sig_handler(int sig)169 [[noreturn]] void sig_handler(int sig)
170 {
171 	char stack_text[10000];
172 
173 #if TORRENT_USE_ASSERTS \
174 	|| defined TORRENT_ASIO_DEBUGGING \
175 	|| defined TORRENT_PROFILE_CALLS \
176 	|| defined TORRENT_DEBUG_BUFFERS
177 	print_backtrace(stack_text, sizeof(stack_text), 30);
178 #elif defined __FUNCTION__
179 	strcpy(stack_text, __FUNCTION__);
180 #else
181 	strcpy(stack_text, "<stack traces disabled>");
182 #endif
183 	char const* name = "<unknown signal>";
184 	switch (sig)
185 	{
186 #define SIG(x) case x: name = #x; break
187 		SIG(SIGSEGV);
188 #ifdef SIGBUS
189 		SIG(SIGBUS);
190 #endif
191 		SIG(SIGINT);
192 		SIG(SIGTERM);
193 		SIG(SIGILL);
194 		SIG(SIGABRT);
195 		SIG(SIGFPE);
196 #ifdef SIGSYS
197 		SIG(SIGSYS);
198 #endif
199 #undef SIG
200 	}
201 	std::printf("signal: (%d) %s caught:\n%s\n", sig, name, stack_text);
202 
203 	output_test_log_to_terminal();
204 
205 	std::exit(128 + sig);
206 }
207 
term_handler()208 [[noreturn]] void term_handler()
209 {
210 	char stack_text[10000];
211 #if TORRENT_USE_ASSERTS \
212 	|| defined TORRENT_ASIO_DEBUGGING \
213 	|| defined TORRENT_PROFILE_CALLS \
214 	|| defined TORRENT_DEBUG_BUFFERS
215 	print_backtrace(stack_text, sizeof(stack_text), 30);
216 #elif defined __FUNCTION__
217 	strcpy(stack_text, __FUNCTION__);
218 #else
219 	strcpy(stack_text, "<stack traces disabled>");
220 #endif
221 	std::printf("\n\nterminate called:\n%s\n\n\n", stack_text);
222 	std::exit(-1);
223 }
224 
print_usage(char const * executable)225 void print_usage(char const* executable)
226 {
227 	std::printf("%s [options] [tests...]\n"
228 		"\n"
229 		"OPTIONS:\n"
230 		"-h,--help            show this help\n"
231 		"-l,--list            list the tests available to run\n"
232 		"-k,--keep            keep files created by the test\n"
233 		"                     regardless of whether it passed or not\n"
234 		"-n,--no-redirect     don't redirect test output to\n"
235 		"                     temporary file, but let it go straight\n"
236 		"                     to stdout\n"
237 		"--stderr-redirect    also redirect stderr in addition to stdout\n"
238 		"\n"
239 		"for tests, specify one or more test names as printed\n"
240 		"by -l. If no test is specified, all tests are run\n", executable);
241 }
242 
change_directory(std::string const & f,error_code & ec)243 void change_directory(std::string const& f, error_code& ec)
244 {
245 	ec.clear();
246 
247 	native_path_string const n = convert_to_native_path_string(f);
248 
249 #ifdef TORRENT_WINDOWS
250 	if (SetCurrentDirectoryW(n.c_str()) == 0)
251 		ec.assign(GetLastError(), system_category());
252 #else
253 	int ret = ::chdir(n.c_str());
254 	if (ret != 0)
255 		ec.assign(errno, system_category());
256 #endif
257 }
258 
259 } // anonymous namespace
260 
261 struct unit_directory_guard
262 {
unit_directory_guardunit_directory_guard263 	explicit unit_directory_guard(std::string d) : dir(std::move(d)) {}
264 	unit_directory_guard(unit_directory_guard const&) = delete;
265 	unit_directory_guard& operator=(unit_directory_guard const&) = delete;
~unit_directory_guardunit_directory_guard266 	~unit_directory_guard()
267 	{
268 		if (keep_files) return;
269 		error_code ec;
270 		std::string const parent_dir = parent_path(dir);
271 		// windows will not allow to remove current dir, so let's change it to root
272 		change_directory(parent_dir, ec);
273 		if (ec)
274 		{
275 			TEST_ERROR("Failed to change directory: " + ec.message());
276 			return;
277 		}
278 		remove_all(dir, ec);
279 #ifdef TORRENT_WINDOWS
280 		if (ec.value() == ERROR_SHARING_VIOLATION)
281 		{
282 			// on windows, files are removed in the background, and we may need
283 			// to wait a little bit
284 			std::this_thread::sleep_for(milliseconds(400));
285 			remove_all(dir, ec);
286 		}
287 #endif
288 		if (ec) std::cerr << "Failed to remove unit test directory: " << ec.message() << "\n";
289 	}
290 private:
291 	std::string dir;
292 };
293 
reset_output()294 void EXPORT reset_output()
295 {
296 	if (current_test == nullptr || current_test->output == nullptr) return;
297 	fflush(stdout);
298 	fflush(stderr);
299 	rewind(current_test->output);
300 #ifdef TORRENT_WINDOWS
301 	int const r = _chsize(fileno(current_test->output), 0);
302 #else
303 	int const r = ftruncate(fileno(current_test->output), 0);
304 #endif
305 	if (r != 0)
306 	{
307 		// this is best effort, it's not the end of the world if we fail
308 		std::cerr << "ftruncate of temporary test output file failed: " << strerror(errno) << "\n";
309 	}
310 }
311 
main(int argc,char const * argv[])312 int EXPORT main(int argc, char const* argv[])
313 {
314 	char const* executable = argv[0];
315 	// skip executable name
316 	++argv;
317 	--argc;
318 
319 	// pick up options
320 	while (argc > 0 && argv[0][0] == '-')
321 	{
322 		if (argv[0] == "-h"_sv || argv[0] == "--help"_sv)
323 		{
324 			print_usage(executable);
325 			return 0;
326 		}
327 
328 		if (argv[0] == "-l"_sv || argv[0] == "--list"_sv)
329 		{
330 			std::printf("TESTS:\n");
331 			for (int i = 0; i < _g_num_unit_tests; ++i)
332 			{
333 				std::printf(" - %s\n", _g_unit_tests[i].name);
334 			}
335 			return 0;
336 		}
337 
338 		if (argv[0] == "-n"_sv || argv[0] == "--no-redirect"_sv)
339 		{
340 			redirect_stdout = false;
341 			redirect_stderr = false;
342 		}
343 
344 		if (argv[0] == "--stderr-redirect"_sv)
345 		{
346 			redirect_stderr = true;
347 		}
348 
349 		if (argv[0] == "-k"_sv || argv[0] == "--keep"_sv)
350 		{
351 			keep_files = true;
352 		}
353 		++argv;
354 		--argc;
355 	}
356 
357 	std::set<std::string> tests_to_run;
358 	bool filter = false;
359 
360 	for (int i = 0; i < argc; ++i)
361 	{
362 		tests_to_run.insert(argv[i]);
363 		filter = true;
364 	}
365 
366 #ifdef O_NONBLOCK
367 	// on darwin, stdout is set to non-blocking mode by default
368 	// which sometimes causes tests to fail with EAGAIN just
369 	// by printing logs
370 	int flags = fcntl(fileno(stdout), F_GETFL, 0);
371 	fcntl(fileno(stdout), F_SETFL, flags & ~O_NONBLOCK);
372 	flags = fcntl(fileno(stderr), F_GETFL, 0);
373 	fcntl(fileno(stderr), F_SETFL, flags & ~O_NONBLOCK);
374 #endif
375 
376 #ifdef _WIN32
377 	// try to suppress hanging the process by windows displaying
378 	// modal dialogs.
379 	SetErrorMode(SEM_NOALIGNMENTFAULTEXCEPT
380 		| SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
381 
382 	SetUnhandledExceptionFilter(&seh_exception_handler);
383 
384 #ifdef _DEBUG
385 	_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
386 	_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
387 #endif
388 
389 #endif
390 
391 	std::set_terminate(term_handler);
392 
393 	signal(SIGSEGV, &sig_handler);
394 #ifdef SIGBUS
395 	signal(SIGBUS, &sig_handler);
396 #endif
397 	signal(SIGILL, &sig_handler);
398 	signal(SIGINT, &sig_handler);
399 	signal(SIGABRT, &sig_handler);
400 	signal(SIGFPE, &sig_handler);
401 #ifdef SIGSYS
402 	signal(SIGSYS, &sig_handler);
403 #endif
404 
405 	int process_id = -1;
406 #ifdef _WIN32
407 	process_id = _getpid();
408 #else
409 	process_id = getpid();
410 #endif
411 	std::string const root_dir = current_working_directory();
412 	std::string const unit_dir_prefix = combine_path(root_dir, "test_tmp_" + std::to_string(process_id) + "_");
413 	std::printf("test: %s\ncwd_prefix = \"%s\"\n", executable, unit_dir_prefix.c_str());
414 
415 	if (_g_num_unit_tests == 0)
416 	{
417 		std::printf("\x1b[31mTEST_ERROR: no unit tests registered\x1b[0m\n");
418 		return 1;
419 	}
420 
421 	if (redirect_stdout) old_stdout = dup(fileno(stdout));
422 	if (redirect_stderr) old_stderr = dup(fileno(stderr));
423 
424 	int num_run = 0;
425 	for (int i = 0; i < _g_num_unit_tests; ++i)
426 	{
427 		if (filter && tests_to_run.count(_g_unit_tests[i].name) == 0)
428 			continue;
429 
430 		std::string const unit_dir = unit_dir_prefix + std::to_string(i);
431 		error_code ec;
432 		create_directory(unit_dir, ec);
433 		if (ec)
434 		{
435 			std::printf("Failed to create unit test directory: %s\n", ec.message().c_str());
436 			output_test_log_to_terminal();
437 			return 1;
438 		}
439 		unit_directory_guard unit_dir_guard{unit_dir};
440 		change_directory(unit_dir, ec);
441 		if (ec)
442 		{
443 			std::printf("Failed to change unit test directory: %s\n", ec.message().c_str());
444 			output_test_log_to_terminal();
445 			return 1;
446 		}
447 
448 		unit_test_t& t = _g_unit_tests[i];
449 
450 		if (redirect_stdout || redirect_stderr)
451 		{
452 			// redirect test output to a temporary file
453 			fflush(stdout);
454 			fflush(stderr);
455 
456 #ifdef TORRENT_MINGW
457 			// mingw has a buggy tmpfile() and tmpname() that needs a . prepended
458 			// to it (or some other directory)
459 			char temp_name[512];
460 			FILE* f = nullptr;
461 			if (tmpnam_s(temp_name + 1, sizeof(temp_name) - 1) == 0)
462 			{
463 				temp_name[0] = '.';
464 				std::printf("using temporary filename %s\n", temp_name);
465 				f = fopen(temp_name, "wb+");
466 			}
467 			else
468 			{
469 				std::printf("failed to generate filename for redirecting "
470 					"output: (%d) %s\n", errno, strerror(errno));
471 			}
472 #else
473 			FILE* f = tmpfile();
474 #endif
475 			if (f != nullptr)
476 			{
477 				int ret1 = 0;
478 				if (redirect_stdout) ret1 |= dup2(fileno(f), fileno(stdout));
479 				if (redirect_stderr) ret1 |= dup2(fileno(f), fileno(stderr));
480 				if (ret1 >= 0)
481 				{
482 					t.output = f;
483 				}
484 				else
485 				{
486 					std::printf("failed to redirect output: (%d) %s\n"
487 						, errno, strerror(errno));
488 				}
489 			}
490 			else
491 			{
492 				std::printf("failed to create temporary file for redirecting "
493 					"output: (%d) %s\n", errno, strerror(errno));
494 			}
495 		}
496 
497 		// get proper interleaving of stderr and stdout
498 		setbuf(stdout, nullptr);
499 		setbuf(stderr, nullptr);
500 
501 		_g_test_idx = i;
502 		current_test = &t;
503 
504 		std::printf("cwd: %s\n", unit_dir.c_str());
505 		std::printf("test-case: %s\n", t.name);
506 		std::mt19937 rng(0x82daf973);
507 		lt::aux::random_engine() = rng;
508 		std::printf("rnd = %x\n", lt::random(0xffffffff));
509 
510 #ifndef BOOST_NO_EXCEPTIONS
511 		try
512 		{
513 #endif
514 
515 #if defined TORRENT_BUILD_SIMULATOR
516 			lt::aux::random_engine().seed(0x82daf973);
517 #endif
518 
519 			_g_test_failures = 0;
520 			(*t.fun)();
521 #ifndef BOOST_NO_EXCEPTIONS
522 		}
523 		catch (boost::system::system_error const& e)
524 		{
525 			char buf[200];
526 			std::snprintf(buf, sizeof(buf), "TEST_ERROR: Terminated with system_error: (%d) [%s] \"%s\""
527 				, e.code().value()
528 				, e.code().category().name()
529 				, e.code().message().c_str());
530 			report_failure(buf, __FILE__, __LINE__);
531 		}
532 		catch (std::exception const& e)
533 		{
534 			char buf[200];
535 			std::snprintf(buf, sizeof(buf), "TEST_ERROR: Terminated with exception: \"%s\"", e.what());
536 			report_failure(buf, __FILE__, __LINE__);
537 		}
538 		catch (...)
539 		{
540 			report_failure("TEST_ERROR: Terminated with unknown exception", __FILE__, __LINE__);
541 		}
542 #endif
543 
544 		if (!tests_to_run.empty()) tests_to_run.erase(t.name);
545 
546 		if (_g_test_failures > 0)
547 		{
548 			output_test_log_to_terminal();
549 		}
550 
551 		t.num_failures = _g_test_failures;
552 		t.run = true;
553 		++num_run;
554 
555 		if (redirect_stdout && t.output)
556 			fclose(t.output);
557 	}
558 
559 	if (redirect_stdout && old_stdout != -1) dup2(old_stdout, fileno(stdout));
560 	if (redirect_stderr && old_stderr != -1) dup2(old_stderr, fileno(stderr));
561 
562 	if (!tests_to_run.empty())
563 	{
564 		std::printf("\x1b[1mUNKONWN tests:\x1b[0m\n");
565 		for (std::set<std::string>::iterator i = tests_to_run.begin()
566 			, end(tests_to_run.end()); i != end; ++i)
567 		{
568 			std::printf("  %s\n", i->c_str());
569 		}
570 	}
571 
572 	if (num_run == 0)
573 	{
574 		std::printf("\x1b[31mTEST_ERROR: no unit tests run\x1b[0m\n");
575 		output_test_log_to_terminal();
576 		return 1;
577 	}
578 
579 	// just in case of premature exits
580 	// make sure we try to clean up some
581 	stop_udp_tracker();
582 	stop_all_proxies();
583 	stop_web_server();
584 	stop_peer();
585 	stop_dht();
586 
587 	if (redirect_stdout) fflush(stdout);
588 	if (redirect_stderr) fflush(stderr);
589 
590 	return print_failures() ? 333 : 0;
591 }
592 
593