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