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\"\nrnd = %x\n"
414 		, executable, unit_dir_prefix.c_str(), lt::random(0xffffffff));
415 
416 	if (_g_num_unit_tests == 0)
417 	{
418 		std::printf("\x1b[31mTEST_ERROR: no unit tests registered\x1b[0m\n");
419 		return 1;
420 	}
421 
422 	if (redirect_stdout) old_stdout = dup(fileno(stdout));
423 	if (redirect_stderr) old_stderr = dup(fileno(stderr));
424 
425 	int num_run = 0;
426 	for (int i = 0; i < _g_num_unit_tests; ++i)
427 	{
428 		if (filter && tests_to_run.count(_g_unit_tests[i].name) == 0)
429 			continue;
430 
431 		std::string const unit_dir = unit_dir_prefix + std::to_string(i);
432 		error_code ec;
433 		create_directory(unit_dir, ec);
434 		if (ec)
435 		{
436 			std::printf("Failed to create unit test directory: %s\n", ec.message().c_str());
437 			output_test_log_to_terminal();
438 			return 1;
439 		}
440 		unit_directory_guard unit_dir_guard{unit_dir};
441 		change_directory(unit_dir, ec);
442 		if (ec)
443 		{
444 			std::printf("Failed to change unit test directory: %s\n", ec.message().c_str());
445 			output_test_log_to_terminal();
446 			return 1;
447 		}
448 
449 		std::printf("cwd: %s\n", unit_dir.c_str());
450 		unit_test_t& t = _g_unit_tests[i];
451 
452 		if (redirect_stdout || redirect_stderr)
453 		{
454 			// redirect test output to a temporary file
455 			fflush(stdout);
456 			fflush(stderr);
457 
458 #ifdef TORRENT_MINGW
459 			// mingw has a buggy tmpfile() and tmpname() that needs a . prepended
460 			// to it (or some other directory)
461 			char temp_name[512];
462 			FILE* f = nullptr;
463 			if (tmpnam_s(temp_name + 1, sizeof(temp_name) - 1) == 0)
464 			{
465 				temp_name[0] = '.';
466 				std::printf("using temporary filename %s\n", temp_name);
467 				f = fopen(temp_name, "wb+");
468 			}
469 			else
470 			{
471 				std::printf("failed to generate filename for redirecting "
472 					"output: (%d) %s\n", errno, strerror(errno));
473 			}
474 #else
475 			FILE* f = tmpfile();
476 #endif
477 			if (f != nullptr)
478 			{
479 				int ret1 = 0;
480 				if (redirect_stdout) ret1 |= dup2(fileno(f), fileno(stdout));
481 				if (redirect_stderr) ret1 |= dup2(fileno(f), fileno(stderr));
482 				if (ret1 >= 0)
483 				{
484 					t.output = f;
485 				}
486 				else
487 				{
488 					std::printf("failed to redirect output: (%d) %s\n"
489 						, errno, strerror(errno));
490 				}
491 			}
492 			else
493 			{
494 				std::printf("failed to create temporary file for redirecting "
495 					"output: (%d) %s\n", errno, strerror(errno));
496 			}
497 		}
498 
499 		// get proper interleaving of stderr and stdout
500 		setbuf(stdout, nullptr);
501 		setbuf(stderr, nullptr);
502 
503 		_g_test_idx = i;
504 		current_test = &t;
505 
506 #ifndef BOOST_NO_EXCEPTIONS
507 		try
508 		{
509 #endif
510 
511 #if defined TORRENT_BUILD_SIMULATOR
512 			lt::aux::random_engine().seed(0x82daf973);
513 #endif
514 
515 			_g_test_failures = 0;
516 			(*t.fun)();
517 #ifndef BOOST_NO_EXCEPTIONS
518 		}
519 		catch (boost::system::system_error const& e)
520 		{
521 			char buf[200];
522 			std::snprintf(buf, sizeof(buf), "TEST_ERROR: Terminated with system_error: (%d) [%s] \"%s\""
523 				, e.code().value()
524 				, e.code().category().name()
525 				, e.code().message().c_str());
526 			report_failure(buf, __FILE__, __LINE__);
527 		}
528 		catch (std::exception const& e)
529 		{
530 			char buf[200];
531 			std::snprintf(buf, sizeof(buf), "TEST_ERROR: Terminated with exception: \"%s\"", e.what());
532 			report_failure(buf, __FILE__, __LINE__);
533 		}
534 		catch (...)
535 		{
536 			report_failure("TEST_ERROR: Terminated with unknown exception", __FILE__, __LINE__);
537 		}
538 #endif
539 
540 		if (!tests_to_run.empty()) tests_to_run.erase(t.name);
541 
542 		if (_g_test_failures > 0)
543 		{
544 			output_test_log_to_terminal();
545 		}
546 
547 		t.num_failures = _g_test_failures;
548 		t.run = true;
549 		++num_run;
550 
551 		if (redirect_stdout && t.output)
552 			fclose(t.output);
553 	}
554 
555 	if (redirect_stdout && old_stdout != -1) dup2(old_stdout, fileno(stdout));
556 	if (redirect_stderr && old_stderr != -1) dup2(old_stderr, fileno(stderr));
557 
558 	if (!tests_to_run.empty())
559 	{
560 		std::printf("\x1b[1mUNKONWN tests:\x1b[0m\n");
561 		for (std::set<std::string>::iterator i = tests_to_run.begin()
562 			, end(tests_to_run.end()); i != end; ++i)
563 		{
564 			std::printf("  %s\n", i->c_str());
565 		}
566 	}
567 
568 	if (num_run == 0)
569 	{
570 		std::printf("\x1b[31mTEST_ERROR: no unit tests run\x1b[0m\n");
571 		output_test_log_to_terminal();
572 		return 1;
573 	}
574 
575 	// just in case of premature exits
576 	// make sure we try to clean up some
577 	stop_udp_tracker();
578 	stop_all_proxies();
579 	stop_web_server();
580 	stop_peer();
581 	stop_dht();
582 
583 	if (redirect_stdout) fflush(stdout);
584 	if (redirect_stderr) fflush(stderr);
585 
586 	return print_failures() ? 333 : 0;
587 }
588 
589