/* * This file Copyright (C) 2013-2017 Mnemosyne LLC * * It may be used under the GNU GPL versions 2 or 3 * or any future license endorsed by Mnemosyne LLC. * */ #include #include #include /* mkstemp() */ #include /* strcmp() */ #ifndef _WIN32 #include /* sync() */ #endif #include "transmission.h" #include "crypto-utils.h" #include "error.h" #include "file.h" #include "platform.h" /* TR_PATH_DELIMETER */ #include "torrent.h" #include "tr-assert.h" #include "trevent.h" #include "variant.h" #include "libtransmission-test.h" bool verbose = false; int current_test = 0; bool should_print(bool pass) { if (!pass) { return true; } if (verbose) { return true; } return false; #ifdef VERBOSE return true; #else return false; #endif } bool libtest_check(char const* file, int line, bool pass, bool condition, char const* condition_str) { if (should_print(pass)) { fprintf(stderr, "%s %s:%d: %s (%s)\n", pass ? "PASS" : "FAIL", file, line, condition_str, condition ? "true": "false"); } return pass; } bool libtest_check_bool(char const* file, int line, bool pass, bool lhs, bool rhs, char const* lhs_str, char const* op_str, char const* rhs_str) { if (should_print(pass)) { fprintf(stderr, "%s %s:%d: %s %s %s (%s %s %s)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs ? "true" : "false", op_str, rhs ? "true" : "false"); } return pass; } bool libtest_check_str(char const* file, int line, bool pass, char const* lhs, char const* rhs, char const* lhs_str, char const* op_str, char const* rhs_str) { if (should_print(pass)) { char const* const lhs_quote = lhs != NULL ? "\"" : ""; char const* const rhs_quote = rhs != NULL ? "\"" : ""; fprintf(stderr, "%s %s:%d: %s %s %s (%s%s%s %s %s%s%s)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs_quote, lhs != NULL ? lhs : "NULL", lhs_quote, op_str, rhs_quote, rhs != NULL ? rhs : "NULL", rhs_quote); } return pass; } static void print_mem(FILE* stream, void const* data, size_t size) { if (data == NULL) { fprintf(stream, "NULL"); return; } if (size == 0) { fprintf(stream, "(no bytes)"); return; } uint8_t const* byte_data = data; fprintf(stream, "x'"); for (size_t i = 0; i < size; ++i) { fprintf(stream, "%02x", (unsigned int)byte_data[i]); } fprintf(stream, "'"); } bool libtest_check_mem(char const* file, int line, bool pass, void const* lhs, void const* rhs, size_t size, char const* lhs_str, char const* op_str, char const* rhs_str) { if (should_print(pass)) { fprintf(stderr, "%s %s:%d: %s %s %s (", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str); print_mem(stderr, lhs, size); fprintf(stderr, " %s ", op_str); print_mem(stderr, rhs, size); fprintf(stderr, ")\n"); } return pass; } bool libtest_check_int(char const* file, int line, bool pass, intmax_t lhs, intmax_t rhs, char const* lhs_str, char const* op_str, char const* rhs_str) { if (should_print(pass)) { fprintf(stderr, "%s %s:%d: %s %s %s (%jd %s %jd)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs, op_str, rhs); } return pass; } bool libtest_check_uint(char const* file, int line, bool pass, uintmax_t lhs, uintmax_t rhs, char const* lhs_str, char const* op_str, char const* rhs_str) { if (should_print(pass)) { fprintf(stderr, "%s %s:%d: %s %s %s (%ju %s %ju)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs, op_str, rhs); } return pass; } bool libtest_check_ptr(char const* file, int line, bool pass, void const* lhs, void const* rhs, char const* lhs_str, char const* op_str, char const* rhs_str) { if (should_print(pass)) { fprintf(stderr, "%s %s:%d: %s %s %s (%p %s %p)\n", pass ? "PASS" : "FAIL", file, line, lhs_str, op_str, rhs_str, lhs, op_str, rhs); } return pass; } int runTests(testFunc const* const tests, int numTests) { int ret = 0; (void)current_test; /* Use test even if we don't have any tests to run */ for (int i = 0; i < numTests; i++) { if ((*tests[i])() != 0) { ++ret; } } return ret; } /*** **** ***/ static char* tr_getcwd(void) { char* result; tr_error* error = NULL; result = tr_sys_dir_get_current(&error); if (result == NULL) { fprintf(stderr, "getcwd error: \"%s\"", error->message); tr_error_free(error); result = tr_strdup(""); } return result; } char* libtest_sandbox_create(void) { char* path = tr_getcwd(); char* sandbox = tr_buildPath(path, "sandbox-XXXXXX", NULL); tr_free(path); tr_sys_dir_create_temp(sandbox, NULL); return tr_sys_path_native_separators(sandbox); } static void rm_rf(char const* killme) { tr_sys_path_info info; if (tr_sys_path_get_info(killme, 0, &info, NULL)) { tr_sys_dir_t odir; if (info.type == TR_SYS_PATH_IS_DIRECTORY && (odir = tr_sys_dir_open(killme, NULL)) != TR_BAD_SYS_DIR) { char const* name; while ((name = tr_sys_dir_read_name(odir, NULL)) != NULL) { if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) { char* tmp = tr_buildPath(killme, name, NULL); rm_rf(tmp); tr_free(tmp); } } tr_sys_dir_close(odir, NULL); } if (verbose) { fprintf(stderr, "cleanup: removing %s\n", killme); } tr_sys_path_remove(killme, NULL); } } void libtest_sandbox_destroy(char const* sandbox) { rm_rf(sandbox); } /*** **** ***/ #define MEM_K 1024 #define MEM_K_STR "KiB" #define MEM_M_STR "MiB" #define MEM_G_STR "GiB" #define MEM_T_STR "TiB" #define DISK_K 1000 #define DISK_K_STR "kB" #define DISK_M_STR "MB" #define DISK_G_STR "GB" #define DISK_T_STR "TB" #define SPEED_K 1000 #define SPEED_K_STR "kB/s" #define SPEED_M_STR "MB/s" #define SPEED_G_STR "GB/s" #define SPEED_T_STR "TB/s" tr_session* libttest_session_init(tr_variant* settings) { size_t len; char const* str; char* sandbox; char* path; tr_quark q; static bool formatters_inited = false; tr_session* session; tr_variant local_settings; tr_variantInitDict(&local_settings, 10); if (settings == NULL) { settings = &local_settings; } sandbox = libtest_sandbox_create(); if (!formatters_inited) { formatters_inited = true; tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR); tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR); tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR); } /* download dir */ q = TR_KEY_download_dir; if (tr_variantDictFindStr(settings, q, &str, &len)) { path = tr_strdup_printf("%s/%*.*s", sandbox, (int)len, (int)len, str); } else { path = tr_buildPath(sandbox, "Downloads", NULL); } tr_sys_dir_create(path, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL); tr_variantDictAddStr(settings, q, path); tr_free(path); /* incomplete dir */ q = TR_KEY_incomplete_dir; if (tr_variantDictFindStr(settings, q, &str, &len)) { path = tr_strdup_printf("%s/%*.*s", sandbox, (int)len, (int)len, str); } else { path = tr_buildPath(sandbox, "Incomplete", NULL); } tr_variantDictAddStr(settings, q, path); tr_free(path); path = tr_buildPath(sandbox, "blocklists", NULL); tr_sys_dir_create(path, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL); tr_free(path); q = TR_KEY_port_forwarding_enabled; if (tr_variantDictFind(settings, q) == NULL) { tr_variantDictAddBool(settings, q, false); } q = TR_KEY_dht_enabled; if (tr_variantDictFind(settings, q) == NULL) { tr_variantDictAddBool(settings, q, false); } q = TR_KEY_message_level; if (tr_variantDictFind(settings, q) == NULL) { tr_variantDictAddInt(settings, q, verbose ? TR_LOG_DEBUG : TR_LOG_ERROR); } session = tr_sessionInit(sandbox, !verbose, settings); tr_free(sandbox); tr_variantFree(&local_settings); return session; } void libttest_session_close(tr_session* session) { char* sandbox; sandbox = tr_strdup(tr_sessionGetConfigDir(session)); tr_sessionClose(session); tr_logFreeQueue(tr_logGetQueue()); session = NULL; libtest_sandbox_destroy(sandbox); tr_free(sandbox); } /*** **** ***/ tr_torrent* libttest_zero_torrent_init(tr_session* session) { int err; size_t metainfo_len; char* metainfo; char const* metainfo_base64; tr_torrent* tor; tr_ctor* ctor; /* 1048576 files-filled-with-zeroes/1048576 4096 files-filled-with-zeroes/4096 512 files-filled-with-zeroes/512 */ metainfo_base64 = "ZDg6YW5ub3VuY2UzMTpodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Fubm91bmNlMTA6Y3JlYXRlZCBi" "eTI1OlRyYW5zbWlzc2lvbi8yLjYxICgxMzQwNykxMzpjcmVhdGlvbiBkYXRlaTEzNTg3MDQwNzVl" "ODplbmNvZGluZzU6VVRGLTg0OmluZm9kNTpmaWxlc2xkNjpsZW5ndGhpMTA0ODU3NmU0OnBhdGhs" "NzoxMDQ4NTc2ZWVkNjpsZW5ndGhpNDA5NmU0OnBhdGhsNDo0MDk2ZWVkNjpsZW5ndGhpNTEyZTQ6" "cGF0aGwzOjUxMmVlZTQ6bmFtZTI0OmZpbGVzLWZpbGxlZC13aXRoLXplcm9lczEyOnBpZWNlIGxl" "bmd0aGkzMjc2OGU2OnBpZWNlczY2MDpRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj" "/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv17" "26aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJtGEx" "Uv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GIQxhJ" "tGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZCS1GI" "QxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8KT9ZC" "S1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9umo/8K" "T9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9e9um" "o/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRhMVL9" "e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMYSbRh" "MVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLUYhDGEm0YTFS/XvbpqP/Ck/WQktRiEMY" "SbRhMVL9e9umo/8KT9ZCS1GIQxhJtGExUv1726aj/wpP1kJLOlf5A+Tz30nMBVuNM2hpV3wg/103" "OnByaXZhdGVpMGVlZQ=="; /* create the torrent ctor */ metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len); TR_ASSERT(metainfo != NULL); TR_ASSERT(metainfo_len > 0); TR_ASSERT(session != NULL); ctor = tr_ctorNew(session); tr_ctorSetMetainfo(ctor, (uint8_t*)metainfo, metainfo_len); tr_ctorSetPaused(ctor, TR_FORCE, true); /* create the torrent */ err = 0; tor = tr_torrentNew(ctor, &err, NULL); TR_ASSERT(err == 0); /* cleanup */ tr_free(metainfo); tr_ctorFree(ctor); return tor; } void libttest_zero_torrent_populate(tr_torrent* tor, bool complete) { for (tr_file_index_t i = 0; i < tor->info.fileCount; ++i) { int err; tr_sys_file_t fd; char* path; char* dirname; tr_file const* file = &tor->info.files[i]; if (!complete && i == 0) { path = tr_strdup_printf("%s%c%s.part", tor->currentDir, TR_PATH_DELIMITER, file->name); } else { path = tr_strdup_printf("%s%c%s", tor->currentDir, TR_PATH_DELIMITER, file->name); } dirname = tr_sys_path_dirname(path, NULL); tr_sys_dir_create(dirname, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL); fd = tr_sys_file_open(path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, 0600, NULL); for (uint64_t j = 0; j < file->length; ++j) { tr_sys_file_write(fd, (!complete && i == 0 && j < tor->info.pieceSize) ? "\1" : "\0", 1, NULL, NULL); } tr_sys_file_close(fd, NULL); tr_free(dirname); tr_free(path); path = tr_torrentFindFile(tor, i); TR_ASSERT(path != NULL); err = errno; TR_ASSERT(tr_sys_path_exists(path, NULL)); errno = err; tr_free(path); } libttest_sync(); libttest_blockingTorrentVerify(tor); if (complete) { TR_ASSERT(tr_torrentStat(tor)->leftUntilDone == 0); } else { TR_ASSERT(tr_torrentStat(tor)->leftUntilDone == tor->info.pieceSize); } } /*** **** ***/ static void onVerifyDone(tr_torrent* tor UNUSED, bool aborted UNUSED, void* done) { *(bool*)done = true; } void libttest_blockingTorrentVerify(tr_torrent* tor) { TR_ASSERT(tor->session != NULL); TR_ASSERT(!tr_amInEventThread(tor->session)); bool done = false; tr_torrentVerify(tor, onVerifyDone, &done); while (!done) { tr_wait_msec(10); } } static void build_parent_dir(char const* path) { char* dir; tr_error* error = NULL; int const tmperr = errno; dir = tr_sys_path_dirname(path, NULL); tr_sys_dir_create(dir, TR_SYS_DIR_CREATE_PARENTS, 0700, &error); TR_ASSERT(error == NULL); tr_free(dir); errno = tmperr; } void libtest_create_file_with_contents(char const* path, void const* payload, size_t n) { tr_sys_file_t fd; int const tmperr = errno; build_parent_dir(path); fd = tr_sys_file_open(path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, 0600, NULL); tr_sys_file_write(fd, payload, n, NULL, NULL); tr_sys_file_close(fd, NULL); libttest_sync(); errno = tmperr; } void libtest_create_file_with_string_contents(char const* path, char const* str) { libtest_create_file_with_contents(path, str, strlen(str)); } void libtest_create_tmpfile_with_contents(char* tmpl, void const* payload, size_t n) { tr_sys_file_t fd; int const tmperr = errno; uint64_t n_left = n; tr_error* error = NULL; build_parent_dir(tmpl); fd = tr_sys_file_open_temp(tmpl, NULL); while (n_left > 0) { uint64_t n; if (!tr_sys_file_write(fd, payload, n_left, &n, &error)) { fprintf(stderr, "Error writing '%s': %s\n", tmpl, error->message); tr_error_free(error); break; } n_left -= n; } tr_sys_file_close(fd, NULL); libttest_sync(); errno = tmperr; } void libttest_sync(void) { #ifndef _WIN32 sync(); #endif }