diff --git a/.gitmodules b/.gitmodules index 1b256d3be..f8c31abc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ path = third-party/miniupnpc url = https://github.com/transmission/miniupnpc branch = post-2.0.20170509-transmission +[submodule "third-party/googletest"] + path = third-party/googletest + url = https://github.com/google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 03157da21..a74020ad8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ set(QT5_MINIMUM 5.2) if(WIN32) foreach(L C CXX) + set(CMAKE_${L}_FLAGS "${CMAKE_${L}_FLAGS} -DWIN32") # Target version (Vista and up) set(CMAKE_${L}_FLAGS "${CMAKE_${L}_FLAGS} -DWINVER=0x0600 -D_WIN32_WINNT=0x0600") # Use Unicode API (although we always use W or A names explicitly) @@ -375,10 +376,14 @@ if(WIN32) endforeach() endif() +## Compiler standard version + if(CMAKE_VERSION VERSION_LESS "3.1") if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17") + elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") endif() else() set(CMAKE_C_STANDARD 99) @@ -387,48 +392,92 @@ else() set(CMAKE_CXX_STANDARD_REQUIRED ON) endif() -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") - set(NEEDED_COMPILER_FLAGS - -Wall - -W - -Wcast-align - -Wfloat-equal - -Wmissing-format-attribute - -Wpointer-arith - -Wredundant-decls - -Wundef - -Wunused-parameter - -Wwrite-strings) +### Compiler Warnings - if(NOT CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_VERSION VERSION_GREATER "3.3") - list(APPEND NEEDED_COMPILER_FLAGS - -Wextra - -Winit-self) - endif() +set(C_WARNING_FLAGS) +set(CXX_WARNING_FLAGS) - if(MINGW) - # Disable excessive warnings since we're using __USE_MINGW_ANSI_STDIO - # Hopefully, any potential issues will be spotted on other platforms - list(APPEND NEEDED_COMPILER_FLAGS -Wno-format) - else() - list(APPEND NEEDED_COMPILER_FLAGS -Wformat-security) - endif() +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) - set(NEEDED_C_COMPILER_FLAGS - ${NEEDED_COMPILER_FLAGS} - -Winline - -Wmissing-declarations - -Wnested-externs - -Wstrict-prototypes) - string(REPLACE ";" " " NEEDED_C_COMPILER_FLAGS_STRING "${NEEDED_C_COMPILER_FLAGS}") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${NEEDED_C_COMPILER_FLAGS_STRING}") +set(WARNING_CANDIDATES + -W + -Wall + -Wextra + -Wcast-align + -Wduplicated-cond + -Wextra-semi + -Wextra-semi-stmt + -Wextra-tokens + -Wfloat-equal + -Wgnu + -Winit-self + -Wint-in-bool-context + -Wlogical-op + -Wmissing-format-attribute + -Wnested-externs + -Wnull-dereference + -Wpointer-arith + -Wredundant-decls + -Wredundant-move + -Wreorder-ctor + -Wrestrict + -Wreturn-std-move + -Wself-assign + -Wself-move + -Wsemicolon-before-method-body + -Wsentinel + -Wshadow + -Wsign-compare + -Wsometimes-uninitialized + -Wstrict-prototypes + -Wstring-conversion + -Wsuggest-destructor-override + -Wsuggest-override + -Wuninitialized + -Wunreachable-code + -Wunused + -Wunused-const-variable + -Wunused-parameter + -Wunused-result + -Wwrite-strings +) - set(NEEDED_CXX_COMPILER_FLAGS - ${NEEDED_COMPILER_FLAGS}) - string(REPLACE ";" " " NEEDED_CXX_COMPILER_FLAGS_STRING "${NEEDED_CXX_COMPILER_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${NEEDED_CXX_COMPILER_FLAGS_STRING}") +if(MINGW) + # Disable excessive warnings since we're using __USE_MINGW_ANSI_STDIO + # Hopefully, any potential issues will be spotted on other platforms + list(APPEND WARNING_CANDIDATES -Wno-format) +else() + list(APPEND WARNING_CANDIDATES -Wformat-security) endif() +foreach(FLAG ${WARNING_CANDIDATES}) + tr_make_id("${FLAG}" FLAG_ID) + + # if available, add to C warnings + set(CACHE_ID "${CMAKE_C_COMPILER_ID}_C_HAS${FLAG_ID}") + string(TOLOWER "${CACHE_ID}" CACHE_ID) + check_c_compiler_flag(${FLAG} ${CACHE_ID}) + if (${CACHE_ID}) + list(APPEND C_WARNING_FLAGS ${FLAG}) + endif() + + # if available, add to CXX warnings + set(CACHE_ID "${CMAKE_CXX_COMPILER_ID}_CXX_HAS${FLAG_ID}") + string(TOLOWER "${CACHE_ID}" CACHE_ID) + check_cxx_compiler_flag(${FLAG} ${CACHE_ID}) + if (${CACHE_ID}) + list(APPEND CXX_WARNING_FLAGS ${FLAG}) + endif() + + unset(CACHE_ID) + unset(FLAG_ID) +endforeach() + +unset(WARNING_CANDIDATES) + +### + include(LargeFileSupport) set(NEEDED_HEADERS @@ -524,18 +573,20 @@ else() endif() if(NOT CMAKE_VERSION VERSION_LESS "3.7.2") - message(STATUS "Looking for clang-tidy") - find_program(CLANG_TIDY clang-tidy) - if (CLANG_TIDY STREQUAL "CLANG_TIDY-NOTFOUND") - message(STATUS "Looking for clang-tidy - not found") - else() - message(STATUS "Looking for clang-tidy - found") - set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY}") - endif() + message(STATUS "Looking for clang-tidy") + find_program(CLANG_TIDY clang-tidy) + if (CLANG_TIDY STREQUAL "CLANG_TIDY-NOTFOUND") + message(STATUS "Looking for clang-tidy - not found") + else() + message(STATUS "Looking for clang-tidy - found") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY}") + endif() endif() if(ENABLE_TESTS) + include(CTest) enable_testing() + add_subdirectory(tests) endif() function(tr_install_web DST_DIR) diff --git a/Makefile.am b/Makefile.am index 8afe5c266..ae46a5c40 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,7 +24,8 @@ SUBDIRS = \ $(CLI_DIR) \ $(GTK_DIR) \ $(MAC_DIR) \ - web + web \ + tests EXTRA_DIST = \ qt \ diff --git a/code_style.sh b/code_style.sh index ba09f33db..94836b207 100755 --- a/code_style.sh +++ b/code_style.sh @@ -27,6 +27,7 @@ xargs \ find \ qt \ + tests \ \( -name '*.cc' -o -name '*.h' \) \ -print0 | xargs \ diff --git a/configure.ac b/configure.ac index 264c6d06d..69bde94d5 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ AC_CONFIG_MACRO_DIR([m4]) dnl AM_CONFIG_HEADER(config.h) AC_CONFIG_SRCDIR(libtransmission/transmission.h) -AM_INIT_AUTOMAKE([1.9 tar-pax no-dist-gzip dist-xz foreign]) +AM_INIT_AUTOMAKE([1.9 tar-pax no-dist-gzip dist-xz foreign subdir-objects]) LT_INIT LT_LIB_M @@ -83,7 +83,7 @@ AC_PROG_CXX AC_C_INLINE if test "x$GCC" = "xyes" ; then - CFLAGS="$CFLAGS -std=gnu99 -ggdb3 -Wall -W -Wpointer-arith -Wformat-security -Wundef -Wcast-align -Wstrict-prototypes -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wnested-externs -Wunused-parameter -Wwrite-strings -Winline -Wfloat-equal" + CFLAGS="$CFLAGS -std=gnu99 -ggdb3 -Wall -W -Wpointer-arith -Wformat-security -Wundef -Wcast-align -Wstrict-prototypes -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wnested-externs -Wunused-parameter -Wwrite-strings -Wfloat-equal" dnl figure out gcc version AC_MSG_CHECKING([gcc version]) @@ -675,6 +675,9 @@ AC_CONFIG_FILES([Makefile web/style/transmission/images/buttons/Makefile web/javascript/Makefile web/javascript/jquery/Makefile + tests/Makefile + tests/gtest/Makefile + tests/libtransmission/Makefile po/Makefile.in]) AC_OUTPUT diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 3b34ed116..386d86d3d 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -6,6 +6,9 @@ endif() include_directories( ${CMAKE_SOURCE_DIR} +) +include_directories( + SYSTEM ${CURL_INCLUDE_DIRS} ${EVENT2_INCLUDE_DIRS} ) diff --git a/gtk/CMakeLists.txt b/gtk/CMakeLists.txt index 28b2589e8..125f7bcd1 100644 --- a/gtk/CMakeLists.txt +++ b/gtk/CMakeLists.txt @@ -1,5 +1,7 @@ project(trgtk) +add_compile_options(${C_WARNING_FLAGS}) + if(WITH_LIBAPPINDICATOR) add_definitions(-DHAVE_LIBAPPINDICATOR) endif() @@ -119,6 +121,9 @@ set(${PROJECT_NAME}_HEADERS include_directories( ${CMAKE_SOURCE_DIR} ${PROJECT_BINARY_DIR} +) +include_directories( + SYSTEM ${LIBAPPINDICATOR_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} diff --git a/gtk/conf.c b/gtk/conf.c index 5c0ca3769..4e8324017 100644 --- a/gtk/conf.c +++ b/gtk/conf.c @@ -39,7 +39,6 @@ #include "util.h" #define MY_CONFIG_NAME "transmission" -#define MY_READABLE_NAME "transmission-gtk" static char* gl_confdir = NULL; diff --git a/gtk/details.c b/gtk/details.c index b64679c98..9b6e3dc54 100644 --- a/gtk/details.c +++ b/gtk/details.c @@ -656,7 +656,7 @@ static char* get_short_date_string(time_t t) tr_localtime_r(&t, &tm); strftime(buf, sizeof(buf), "%d %b %Y", &tm); return g_locale_to_utf8(buf, -1, NULL, NULL, NULL); -}; +} static void refreshInfo(struct DetailsImpl* di, tr_torrent** torrents, int n) { @@ -1993,7 +1993,7 @@ static void setPeerViewColumns(GtkTreeView* peer_view) so create a non-visible column and assign it as the 'expander column. */ { - GtkTreeViewColumn* c = gtk_tree_view_column_new(); + c = gtk_tree_view_column_new(); gtk_tree_view_column_set_visible(c, FALSE); gtk_tree_view_append_column(GTK_TREE_VIEW(peer_view), c); gtk_tree_view_set_expander_column(GTK_TREE_VIEW(peer_view), c); @@ -2374,7 +2374,6 @@ static void refreshTracker(struct DetailsImpl* di, tr_torrent** torrents, int n) if (g_hash_table_lookup(hash, gstr->str) == NULL) { GtkTreePath* p; - GtkTreeIter iter; GtkTreeRowReference* ref; gtk_list_store_insert_with_values(store, &iter, -1, diff --git a/gtk/file-list.c b/gtk/file-list.c index 3e74371f3..d80ecfd45 100644 --- a/gtk/file-list.c +++ b/gtk/file-list.c @@ -22,9 +22,7 @@ #include "tr-prefs.h" #include "util.h" -#define TR_DOWNLOAD_KEY "tr-download-key" #define TR_COLUMN_ID_KEY "tr-model-column-id-key" -#define TR_PRIORITY_KEY "tr-priority-key" enum { diff --git a/gtk/icons.c b/gtk/icons.c index 5ae71a88a..efc63a332 100644 --- a/gtk/icons.c +++ b/gtk/icons.c @@ -60,19 +60,17 @@ static int get_size_in_pixels(GtkWidget* widget, GtkIconSize icon_size) static IconCache* icon_cache_new(GtkWidget* for_widget, int icon_size) { - IconCache* icon_cache; - g_return_val_if_fail(for_widget != NULL, NULL); - icon_cache = g_new0(IconCache, 1); - icon_cache->icon_theme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(for_widget)); - icon_cache->icon_size = get_size_in_pixels(for_widget, icon_size); - icon_cache->cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); + IconCache* icons = g_new0(IconCache, 1); + icons->icon_theme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(for_widget)); + icons->icon_size = get_size_in_pixels(for_widget, icon_size); + icons->cache = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); - g_hash_table_insert(icon_cache->cache, (void*)VOID_PIXBUF_KEY, create_void_pixbuf(icon_cache->icon_size, - icon_cache->icon_size)); + g_hash_table_insert(icons->cache, (void*)VOID_PIXBUF_KEY, create_void_pixbuf(icons->icon_size, + icons->icon_size)); - return icon_cache; + return icons; } static char const* _icon_cache_get_icon_key(GIcon* icon) @@ -183,7 +181,7 @@ static GdkPixbuf* _get_icon_pixbuf(GIcon* icon, int size, GtkIconTheme* theme) return NULL; } -static GdkPixbuf* icon_cache_get_mime_type_icon(IconCache* icon_cache, char const* mime_type) +static GdkPixbuf* icon_cache_get_mime_type_icon(IconCache* icons, char const* mime_type) { GIcon* icon; char const* key = NULL; @@ -197,7 +195,7 @@ static GdkPixbuf* icon_cache_get_mime_type_icon(IconCache* icon_cache, char cons key = VOID_PIXBUF_KEY; } - pixbuf = g_hash_table_lookup(icon_cache->cache, key); + pixbuf = g_hash_table_lookup(icons->cache, key); if (pixbuf != NULL) { @@ -206,11 +204,11 @@ static GdkPixbuf* icon_cache_get_mime_type_icon(IconCache* icon_cache, char cons return pixbuf; } - pixbuf = _get_icon_pixbuf(icon, icon_cache->icon_size, icon_cache->icon_theme); + pixbuf = _get_icon_pixbuf(icon, icons->icon_size, icons->icon_theme); if (pixbuf != NULL) { - g_hash_table_insert(icon_cache->cache, (gpointer)key, g_object_ref(pixbuf)); + g_hash_table_insert(icons->cache, (gpointer)key, g_object_ref(pixbuf)); } g_object_unref(G_OBJECT(icon)); diff --git a/gtk/notify.c b/gtk/notify.c index 18b6ca746..0b610fa77 100644 --- a/gtk/notify.c +++ b/gtk/notify.c @@ -74,7 +74,7 @@ static void get_capabilities_callback(GObject* source, GAsyncResult* res, gpoint g_variant_unref(result); } -static void g_signal_callback(GDBusProxy* proxy UNUSED, char* sender_name UNUSED, char* signal_name, GVariant* params, +static void g_signal_callback(GDBusProxy* dbus_proxy UNUSED, char* sender_name UNUSED, char* signal_name, GVariant* params, gpointer user_data UNUSED) { guint id; diff --git a/gtk/tr-core.c b/gtk/tr-core.c index 8b204ca61..7550d01bb 100644 --- a/gtk/tr-core.c +++ b/gtk/tr-core.c @@ -87,7 +87,7 @@ static int core_is_disposed(TrCore const* core) return core == NULL || core->priv->sorted_model == NULL; } -G_DEFINE_TYPE_WITH_CODE(TrCore, tr_core, G_TYPE_OBJECT, G_ADD_PRIVATE(TrCore)); +G_DEFINE_TYPE_WITH_CODE(TrCore, tr_core, G_TYPE_OBJECT, G_ADD_PRIVATE(TrCore)) static void core_dispose(GObject* o) { diff --git a/gtk/tr-prefs.c b/gtk/tr-prefs.c index 0622f322e..071eb71c1 100644 --- a/gtk/tr-prefs.c +++ b/gtk/tr-prefs.c @@ -98,7 +98,7 @@ static gboolean spun_cb_idle(gpointer spin) struct spin_idle_data* data = g_object_get_data(o, IDLE_DATA); /* has the user stopped making changes? */ - if (g_timer_elapsed(data->last_change, NULL) > 0.33F) + if (g_timer_elapsed(data->last_change, NULL) > 0.33) { /* update the core */ tr_quark const key = GPOINTER_TO_INT(g_object_get_data(o, PREF_KEY)); @@ -804,8 +804,6 @@ static GtkWidget* remotePage(GObject* core) GtkCellRenderer* r; GtkTreeSelection* sel; GtkTreeView* v; - GtkWidget* w; - GtkWidget* h; page->store = GTK_LIST_STORE(m); w = gtk_tree_view_new_with_model(m); diff --git a/gtk/tr-window.c b/gtk/tr-window.c index e4c592af7..f1a05b550 100644 --- a/gtk/tr-window.c +++ b/gtk/tr-window.c @@ -710,13 +710,13 @@ GtkWidget* gtr_window_new(GtkApplication* app, GtkUIManager* ui_mgr, TrCore* cor { /* this is to determine the maximum width/height for the label */ - int w = 0; - int h = 0; + int width = 0; + int height = 0; PangoLayout* pango_layout; pango_layout = gtk_widget_create_pango_layout(ul_lb, "999.99 kB/s"); - pango_layout_get_pixel_size(pango_layout, &w, &h); - gtk_widget_set_size_request(ul_lb, w, h); - gtk_widget_set_size_request(dl_lb, w, h); + pango_layout_get_pixel_size(pango_layout, &width, &height); + gtk_widget_set_size_request(ul_lb, width, height); + gtk_widget_set_size_request(dl_lb, width, height); g_object_set(ul_lb, "halign", GTK_ALIGN_END, "valign", GTK_ALIGN_CENTER, NULL); g_object_set(dl_lb, "halign", GTK_ALIGN_END, "valign", GTK_ALIGN_CENTER, NULL); g_object_unref(G_OBJECT(pango_layout)); diff --git a/gtk/util.c b/gtk/util.c index 1efc8064c..d517c03fb 100644 --- a/gtk/util.c +++ b/gtk/util.c @@ -374,8 +374,8 @@ char const* gtr_get_help_uri(void) if (uri == NULL) { - char const* fmt = "https://transmissionbt.com/help/gtk/%d.%dx"; - uri = g_strdup_printf(fmt, MAJOR_VERSION, MINOR_VERSION / 10); + uri = g_strdup_printf("https://transmissionbt.com/help/gtk/%d.%dx", + MAJOR_VERSION, MINOR_VERSION / 10); } return uri; diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index 672cd0e0b..fb8d6f84b 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -1,91 +1,96 @@ project(libtr) -configure_file(version.h.in version.h) +configure_file( + version.h.in + version.h +) -set(THIRD_PARTY_SOURCES ConvertUTF.c jsonsl.c wildmat.c) -if(CMAKE_C_COMPILER_ID STREQUAL "GNU") - set(DISABLE_WARNINGS -w) -elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") - set(DISABLE_WARNINGS -w) -elseif(CMAKE_C_COMPILER_ID MATCHES "SunPro") - set(DISABLE_WARNINGS -erroff) -elseif(MSVC) - set(DISABLE_WARNINGS /w) -endif() -set_source_files_properties(${THIRD_PARTY_SOURCES} PROPERTIES COMPILE_FLAGS ${DISABLE_WARNINGS}) +set(PROJECT_FILES + announcer.c + announcer-http.c + announcer-udp.c + bandwidth.c + bitfield.c + blocklist.c + cache.c + clients.c + completion.c + crypto.c + crypto-utils.c + crypto-utils-cyassl.c + crypto-utils-fallback.c + crypto-utils-openssl.c + crypto-utils-polarssl.c + error.c + fdlimit.c + file.c + file-posix.c + file-win32.c + handshake.c + history.c + inout.c + list.c + log.c + magnet.c + makemeta.c + metainfo.c + natpmp.c + net.c + peer-io.c + peer-mgr.c + peer-msgs.c + platform.c + platform-quota.c + port-forwarding.c + ptrarray.c + quark.c + resume.c + rpcimpl.c + rpc-server.c + session.c + session-id.c + subprocess-posix.c + subprocess-win32.c + stats.c + torrent.c + torrent-ctor.c + torrent-magnet.c + tr-dht.c + trevent.c + tr-assert.c + tr-getopt.c + tr-lpd.c + tr-udp.c + tr-utp.c + upnp.c + utils.c + variant-benc.c + variant.c + variant-json.c + verify.c + watchdir.c + watchdir-generic.c + watchdir-inotify.c + watchdir-kqueue.c + watchdir-win32.c + web.c + webseed.c +) + +string(REPLACE ";" " " C_WARNING_FLAGS_STR "${C_WARNING_FLAGS}") +foreach(FILE ${PROJECT_FILES}) + set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "${C_WARNING_FLAGS_STR}") +endforeach() + +set(THIRD_PARTY_FILES + ConvertUTF.c + jsonsl.c + wildmat.c +) set(${PROJECT_NAME}_SOURCES - announcer.c - announcer-http.c - announcer-udp.c - bandwidth.c - bitfield.c - blocklist.c - cache.c - clients.c - completion.c - ConvertUTF.c - crypto.c - crypto-utils.c - crypto-utils-cyassl.c - crypto-utils-fallback.c - crypto-utils-openssl.c - crypto-utils-polarssl.c - error.c - fdlimit.c - file.c - file-posix.c - file-win32.c - handshake.c - history.c - inout.c - list.c - log.c - magnet.c - makemeta.c - metainfo.c - natpmp.c - net.c - peer-io.c - peer-mgr.c - peer-msgs.c - platform.c - platform-quota.c - port-forwarding.c - ptrarray.c - quark.c - resume.c - rpcimpl.c - rpc-server.c - session.c - session-id.c - subprocess-posix.c - subprocess-win32.c - stats.c - torrent.c - torrent-ctor.c - torrent-magnet.c - tr-dht.c - trevent.c - tr-assert.c - tr-getopt.c - tr-lpd.c - tr-udp.c - tr-utp.c - upnp.c - utils.c - variant-benc.c - variant.c - variant-json.c - verify.c - watchdir.c - watchdir-generic.c - watchdir-inotify.c - watchdir-kqueue.c - watchdir-win32.c - web.c - webseed.c - wildmat.c + ${PROJECT_FILES} + ${THIRD_PARTY_FILES} ) set_source_files_properties(crypto-utils-fallback.c PROPERTIES HEADER_FILE_ONLY ON) @@ -226,6 +231,10 @@ endif() include_directories( ${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR} +) + +include_directories( + SYSTEM ${ZLIB_INCLUDE_DIRS} ${CRYPTO_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS} @@ -238,11 +247,11 @@ include_directories( ) if(ICONV_FOUND) - include_directories(${ICONV_INCLUDE_DIRS}) + include_directories(SYSTEM ${ICONV_INCLUDE_DIRS}) endif() if(ENABLE_UTP) - include_directories(${TP_TOP}/libutp) + include_directories(SYSTEM ${TP_TOP}/libutp) endif() add_library(${TR_NAME} STATIC @@ -283,49 +292,3 @@ endif() if(WIN32) target_link_libraries(${TR_NAME} crypt32 shlwapi) endif() - -if(ENABLE_TESTS) - add_library(${TR_NAME}-test STATIC - libtransmission-test.c - libtransmission-test.h - ) - - target_link_libraries(${TR_NAME}-test ${TR_NAME}) - set_property(TARGET ${TR_NAME}-test PROPERTY FOLDER "UnitTests") - - set(crypto-test_ADD_SOURCES crypto-test-ref.h) - set(subprocess-test_ADD_SOURCES subprocess-test.cmd) - - set(watchdir@generic-test_DEFINITIONS WATCHDIR_TEST_FORCE_GENERIC) - - foreach(T bitfield blocklist clients crypto error file history json magnet makemeta metainfo move peer-msgs quark rename rpc - session subprocess tr-getopt utils variant watchdir watchdir@generic) - set(TP ${TR_NAME}-test-${T}) - if(T MATCHES "^([^@]+)@.+$") - string(REPLACE "@" "-" TP "${TP}") - string(REPLACE "@" "-" T_NAME "${T}") - set(${TP}_TEST_BASENAME "${CMAKE_MATCH_1}") - else() - set(T_NAME "${T}") - set(${TP}_TEST_BASENAME "${T}") - endif() - add_executable(${TP} ${${TP}_TEST_BASENAME}-test.c ${${T}-test_ADD_SOURCES}) - target_link_libraries(${TP} ${TR_NAME} ${TR_NAME}-test) - if(DEFINED ${T}-test_DEFINITIONS) - target_compile_definitions(${TP} PRIVATE ${${T}-test_DEFINITIONS}) - endif() - add_test(NAME ${T_NAME}-test COMMAND ${TP}) - set_property(TARGET ${TP} PROPERTY FOLDER "UnitTests") - endforeach() - - if(WIN32) - add_custom_command(TARGET ${TR_NAME}-test-subprocess PRE_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/subprocess-test.cmd - $/${TR_NAME}-test-subprocess.cmd) - endif() -endif() - -if(INSTALL_LIB) - install(TARGETS ${TR_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(FILES ${${PROJECT_NAME}_PUBLIC_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${TR_NAME}) -endif() diff --git a/libtransmission/Makefile.am b/libtransmission/Makefile.am index d9a61c12f..f1a9679a4 100644 --- a/libtransmission/Makefile.am +++ b/libtransmission/Makefile.am @@ -37,6 +37,7 @@ libtransmission_a_SOURCES = \ handshake.c \ history.c \ inout.c \ + jsonsl.c \ list.c \ log.c \ magnet.c \ @@ -133,7 +134,6 @@ noinst_HEADERS = \ inout.h \ jsonsl.c \ jsonsl.h \ - libtransmission-test.h \ list.h \ log.h \ magnet.h \ @@ -180,31 +180,6 @@ noinst_HEADERS = \ web.h \ webseed.h -TESTS = \ - bitfield-test \ - blocklist-test \ - clients-test \ - crypto-test \ - error-test \ - file-test \ - history-test \ - json-test \ - magnet-test \ - makemeta-test \ - metainfo-test \ - move-test \ - peer-msgs-test \ - quark-test \ - rename-test \ - rpc-test \ - session-test \ - subprocess-test \ - tr-getopt-test \ - utils-test \ - variant-test \ - watchdir-test \ - watchdir-generic-test - noinst_PROGRAMS = $(TESTS) apps_ldadd = \ @@ -222,100 +197,3 @@ apps_ldadd = \ @ZLIB_LIBS@ \ ${LIBM} -TEST_SOURCES = libtransmission-test.c - -bitfield_test_SOURCES = bitfield-test.c $(TEST_SOURCES) -bitfield_test_LDADD = ${apps_ldadd} -bitfield_test_LDFLAGS = ${apps_ldflags} - -blocklist_test_SOURCES = blocklist-test.c $(TEST_SOURCES) -blocklist_test_LDADD = ${apps_ldadd} -blocklist_test_LDFLAGS = ${apps_ldflags} - -clients_test_SOURCES = clients-test.c $(TEST_SOURCES) -clients_test_LDADD = ${apps_ldadd} -clients_test_LDFLAGS = ${apps_ldflags} - -crypto_test_SOURCES = crypto-test.c crypto-test-ref.h $(TEST_SOURCES) -crypto_test_LDADD = ${apps_ldadd} -crypto_test_LDFLAGS = ${apps_ldflags} - -error_test_SOURCES = error-test.c $(TEST_SOURCES) -error_test_LDADD = ${apps_ldadd} -error_test_LDFLAGS = ${apps_ldflags} - -file_test_SOURCES = file-test.c $(TEST_SOURCES) -file_test_LDADD = ${apps_ldadd} -file_test_LDFLAGS = ${apps_ldflags} - -history_test_SOURCES = history-test.c $(TEST_SOURCES) -history_test_LDADD = ${apps_ldadd} -history_test_LDFLAGS = ${apps_ldflags} - -json_test_SOURCES = json-test.c $(TEST_SOURCES) -json_test_LDADD = ${apps_ldadd} -json_test_LDFLAGS = ${apps_ldflags} - -quark_test_SOURCES = quark-test.c $(TEST_SOURCES) -quark_test_LDADD = ${apps_ldadd} -quark_test_LDFLAGS = ${apps_ldflags} - -magnet_test_SOURCES = magnet-test.c $(TEST_SOURCES) -magnet_test_LDADD = ${apps_ldadd} -magnet_test_LDFLAGS = ${apps_ldflags} - -metainfo_test_SOURCES = metainfo-test.c $(TEST_SOURCES) -metainfo_test_LDADD = ${apps_ldadd} -metainfo_test_LDFLAGS = ${apps_ldflags} - -makemeta_test_SOURCES = makemeta-test.c $(TEST_SOURCES) -makemeta_test_LDADD = ${apps_ldadd} -makemeta_test_LDFLAGS = ${apps_ldflags} - -move_test_SOURCES = move-test.c $(TEST_SOURCES) -move_test_LDADD = ${apps_ldadd} -move_test_LDFLAGS = ${apps_ldflags} - -peer_msgs_test_SOURCES = peer-msgs-test.c $(TEST_SOURCES) -peer_msgs_test_LDADD = ${apps_ldadd} -peer_msgs_test_LDFLAGS = ${apps_ldflags} - -rpc_test_SOURCES = rpc-test.c $(TEST_SOURCES) -rpc_test_LDADD = ${apps_ldadd} -rpc_test_LDFLAGS = ${apps_ldflags} - -session_test_SOURCES = session-test.c $(TEST_SOURCES) -session_test_LDADD = ${apps_ldadd} -session_test_LDFLAGS = ${apps_ldflags} - -subprocess_test_SOURCES = subprocess-test.c $(TEST_SOURCES) -subprocess_test_LDADD = ${apps_ldadd} -subprocess_test_LDFLAGS = ${apps_ldflags} - -tr_getopt_test_SOURCES = tr-getopt-test.c $(TEST_SOURCES) -tr_getopt_test_LDADD = ${apps_ldadd} -tr_getopt_test_LDFLAGS = ${apps_ldflags} - -utils_test_SOURCES = utils-test.c $(TEST_SOURCES) -utils_test_LDADD = ${apps_ldadd} -utils_test_LDFLAGS = ${apps_ldflags} - -variant_test_SOURCES = variant-test.c $(TEST_SOURCES) -variant_test_LDADD = ${apps_ldadd} -variant_test_LDFLAGS = ${apps_ldflags} - -watchdir_test_SOURCES = watchdir-test.c $(TEST_SOURCES) -watchdir_test_LDADD = ${apps_ldadd} -watchdir_test_LDFLAGS = ${apps_ldflags} - -watchdir_generic_test_SOURCES = watchdir-test.c $(TEST_SOURCES) -watchdir_generic_test_LDADD = ${apps_ldadd} -watchdir_generic_test_LDFLAGS = ${apps_ldflags} -watchdir_generic_test_CPPFLAGS = -DWATCHDIR_TEST_FORCE_GENERIC $(AM_CPPFLAGS) - -rename_test_SOURCES = rename-test.c $(TEST_SOURCES) -rename_test_LDADD = ${apps_ldadd} -rename_test_LDFLAGS = ${apps_ldflags} - -EXTRA_DIST = \ - subprocess-test.cmd diff --git a/libtransmission/announcer-common.h b/libtransmission/announcer-common.h index ee162d1da..f75058171 100644 --- a/libtransmission/announcer-common.h +++ b/libtransmission/announcer-common.h @@ -8,7 +8,7 @@ #pragma once -#ifndef __LIBTRANSMISSION_ANNOUNCER_MODULE__ +#ifndef LIBTRANSMISSION_ANNOUNCER_MODULE #error only the libtransmission announcer module should #include this header. #endif diff --git a/libtransmission/announcer-http.c b/libtransmission/announcer-http.c index dbeee1daa..5a34cf70a 100644 --- a/libtransmission/announcer-http.c +++ b/libtransmission/announcer-http.c @@ -13,7 +13,7 @@ #include #include /* for HTTP_OK */ -#define __LIBTRANSMISSION_ANNOUNCER_MODULE__ +#define LIBTRANSMISSION_ANNOUNCER_MODULE #include "transmission.h" #include "announcer-common.h" @@ -396,8 +396,6 @@ static void on_scrape_done(tr_session* session, bool did_connect, bool did_timeo int64_t intVal; tr_variant* files; tr_variant* flags; - size_t len; - char const* str; bool const variant_loaded = tr_variantFromBenc(&top, msg, msglen) == 0; if (tr_env_key_exists("TR_CURL_VERBOSE")) @@ -424,6 +422,8 @@ static void on_scrape_done(tr_session* session, bool did_connect, bool did_timeo if (variant_loaded) { + size_t len; + char const* str; if (tr_variantDictFindStr(&top, TR_KEY_failure_reason, &str, &len)) { response->errmsg = tr_strndup(str, len); diff --git a/libtransmission/announcer-udp.c b/libtransmission/announcer-udp.c index e141ba27d..88e748b20 100644 --- a/libtransmission/announcer-udp.c +++ b/libtransmission/announcer-udp.c @@ -13,7 +13,7 @@ #include #include -#define __LIBTRANSMISSION_ANNOUNCER_MODULE__ +#define LIBTRANSMISSION_ANNOUNCER_MODULE #include "transmission.h" #include "announcer.h" diff --git a/libtransmission/announcer.c b/libtransmission/announcer.c index 1df0299f4..cb74f042d 100644 --- a/libtransmission/announcer.c +++ b/libtransmission/announcer.c @@ -14,7 +14,7 @@ #include #include /* evtimer */ -#define __LIBTRANSMISSION_ANNOUNCER_MODULE__ +#define LIBTRANSMISSION_ANNOUNCER_MODULE #include "transmission.h" #include "announcer.h" diff --git a/libtransmission/bitfield-test.c b/libtransmission/bitfield-test.c deleted file mode 100644 index 3ba9f41fe..000000000 --- a/libtransmission/bitfield-test.c +++ /dev/null @@ -1,244 +0,0 @@ -/* - * This file Copyright (C) 2010-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* strlen() */ -#include "transmission.h" -#include "crypto-utils.h" -#include "bitfield.h" -#include "utils.h" /* tr_free */ - -#include "libtransmission-test.h" - -static int test_bitfield_count_range(void) -{ - int begin; - int end; - int count1; - int count2; - int const bitCount = 100 + tr_rand_int_weak(1000); - tr_bitfield bf; - - /* generate a random bitfield */ - tr_bitfieldConstruct(&bf, bitCount); - - for (int i = 0, n = tr_rand_int_weak(bitCount); i < n; ++i) - { - tr_bitfieldAdd(&bf, tr_rand_int_weak(bitCount)); - } - - begin = tr_rand_int_weak(bitCount); - - do - { - end = tr_rand_int_weak(bitCount); - } - while (end == begin); - - /* ensure end <= begin */ - if (end < begin) - { - int const tmp = begin; - begin = end; - end = tmp; - } - - /* test the bitfield */ - count1 = 0; - - for (int i = begin; i < end; ++i) - { - if (tr_bitfieldHas(&bf, i)) - { - ++count1; - } - } - - count2 = tr_bitfieldCountRange(&bf, begin, end); - check_int(count1, ==, count2); - - /* cleanup */ - tr_bitfieldDestruct(&bf); - return 0; -} - -static int test_bitfields(void) -{ - unsigned int bitcount = 500; - tr_bitfield field; - - tr_bitfieldConstruct(&field, bitcount); - - /* test tr_bitfieldAdd */ - for (unsigned int i = 0; i < bitcount; i++) - { - if (i % 7 == 0) - { - tr_bitfieldAdd(&field, i); - } - } - - for (unsigned int i = 0; i < bitcount; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (i % 7 == 0)); - } - - /* test tr_bitfieldAddRange */ - tr_bitfieldAddRange(&field, 0, bitcount); - - for (unsigned int i = 0; i < bitcount; i++) - { - check(tr_bitfieldHas(&field, i)); - } - - /* test tr_bitfieldRem */ - for (unsigned int i = 0; i < bitcount; i++) - { - if (i % 7 != 0) - { - tr_bitfieldRem(&field, i); - } - } - - for (unsigned int i = 0; i < bitcount; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (i % 7 == 0)); - } - - /* test tr_bitfieldRemRange in the middle of a boundary */ - tr_bitfieldAddRange(&field, 0, 64); - tr_bitfieldRemRange(&field, 4, 21); - - for (unsigned int i = 0; i < 64; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (i < 4 || i >= 21)); - } - - /* test tr_bitfieldRemRange on the boundaries */ - tr_bitfieldAddRange(&field, 0, 64); - tr_bitfieldRemRange(&field, 8, 24); - - for (unsigned int i = 0; i < 64; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (i < 8 || i >= 24)); - } - - /* test tr_bitfieldRemRange when begin & end is on the same word */ - tr_bitfieldAddRange(&field, 0, 64); - tr_bitfieldRemRange(&field, 4, 5); - - for (unsigned int i = 0; i < 64; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (i < 4 || i >= 5)); - } - - /* test tr_bitfieldAddRange */ - tr_bitfieldRemRange(&field, 0, 64); - tr_bitfieldAddRange(&field, 4, 21); - - for (unsigned int i = 0; i < 64; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (4 <= i && i < 21)); - } - - /* test tr_bitfieldAddRange on the boundaries */ - tr_bitfieldRemRange(&field, 0, 64); - tr_bitfieldAddRange(&field, 8, 24); - - for (unsigned int i = 0; i < 64; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (8 <= i && i < 24)); - } - - /* test tr_bitfieldAddRange when begin & end is on the same word */ - tr_bitfieldRemRange(&field, 0, 64); - tr_bitfieldAddRange(&field, 4, 5); - - for (unsigned int i = 0; i < 64; i++) - { - check_bool(tr_bitfieldHas(&field, i), ==, (4 <= i && i < 5)); - } - - tr_bitfieldDestruct(&field); - return 0; -} - -static int test_bitfield_has_all_none(void) -{ - tr_bitfield field; - - tr_bitfieldConstruct(&field, 3); - - check(!tr_bitfieldHasAll(&field)); - check(tr_bitfieldHasNone(&field)); - - tr_bitfieldAdd(&field, 0); - check(!tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldRem(&field, 0); - tr_bitfieldAdd(&field, 1); - check(!tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldRem(&field, 1); - tr_bitfieldAdd(&field, 2); - check(!tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldAdd(&field, 0); - tr_bitfieldAdd(&field, 1); - check(tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldSetHasNone(&field); - check(!tr_bitfieldHasAll(&field)); - check(tr_bitfieldHasNone(&field)); - - tr_bitfieldSetHasAll(&field); - check(tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldDestruct(&field); - tr_bitfieldConstruct(&field, 0); - - check(!tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldSetHasNone(&field); - check(!tr_bitfieldHasAll(&field)); - check(tr_bitfieldHasNone(&field)); - - tr_bitfieldSetHasAll(&field); - check(tr_bitfieldHasAll(&field)); - check(!tr_bitfieldHasNone(&field)); - - tr_bitfieldDestruct(&field); - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - test_bitfields, - test_bitfield_has_all_none - }; - - int ret = runTests(tests, NUM_TESTS(tests)); - - /* bitfield count range */ - for (int l = 0; l < 10000; ++l) - { - if (test_bitfield_count_range() != 0) - { - ++ret; - } - } - - return ret; -} diff --git a/libtransmission/bitfield.h b/libtransmission/bitfield.h index 0c56d24e3..05c7883aa 100644 --- a/libtransmission/bitfield.h +++ b/libtransmission/bitfield.h @@ -13,6 +13,9 @@ #endif #include "transmission.h" +#include "tr-macros.h" + +TR_BEGIN_DECLS /** @brief Implementation of the BitTorrent spec's Bitfield array of bits */ typedef struct tr_bitfield @@ -91,3 +94,5 @@ static inline bool tr_bitfieldHasNone(tr_bitfield const* b) } bool tr_bitfieldHas(tr_bitfield const* b, size_t n); + +TR_END_DECLS diff --git a/libtransmission/blocklist-test.c b/libtransmission/blocklist-test.c deleted file mode 100644 index 3f443ec52..000000000 --- a/libtransmission/blocklist-test.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include -#include /* strlen() */ - -#include "transmission.h" -#include "blocklist.h" -#include "file.h" -#include "net.h" -#include "session.h" /* tr_sessionIsAddressBlocked() */ -#include "utils.h" - -#include "libtransmission-test.h" - -static char const* contents1 = - "10.5.6.7/8\n" - "Austin Law Firm:216.16.1.144-216.16.1.151\n" - "Sargent Controls and Aerospace:216.19.18.0-216.19.18.255\n" - "Corel Corporation:216.21.157.192-216.21.157.223\n" - "Fox Speed Channel:216.79.131.192-216.79.131.223\n"; - -static char const* contents2 = - "10.5.6.7/8\n" - "Austin Law Firm:216.16.1.144-216.16.1.151\n" - "Sargent Controls and Aerospace:216.19.18.0-216.19.18.255\n" - "Corel Corporation:216.21.157.192-216.21.157.223\n" - "Fox Speed Channel:216.79.131.192-216.79.131.223\n" - "Evilcorp:216.88.88.0-216.88.88.255\n"; - -static void create_text_file(char const* path, char const* contents) -{ - tr_sys_file_t fd; - char* dir; - - dir = tr_sys_path_dirname(path, NULL); - tr_sys_dir_create(dir, TR_SYS_DIR_CREATE_PARENTS, 0700, NULL); - tr_free(dir); - - 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, contents, strlen(contents), NULL, NULL); - tr_sys_file_close(fd, NULL); - - libttest_sync(); -} - -static bool address_is_blocked(tr_session* session, char const* address_str) -{ - struct tr_address addr; - tr_address_from_string(&addr, address_str); - return tr_sessionIsAddressBlocked(session, &addr); -} - -static int test_parsing(void) -{ - char* path; - tr_session* session; - - /* init the session */ - session = libttest_session_init(NULL); - check(!tr_blocklistExists(session)); - check_int(tr_blocklistGetRuleCount(session), ==, 0); - - /* init the blocklist */ - path = tr_buildPath(tr_sessionGetConfigDir(session), "blocklists", "level1", NULL); - create_text_file(path, contents1); - tr_free(path); - tr_sessionReloadBlocklists(session); - check(tr_blocklistExists(session)); - check_int(tr_blocklistGetRuleCount(session), ==, 5); - - /* enable the blocklist */ - check(!tr_blocklistIsEnabled(session)); - tr_blocklistSetEnabled(session, true); - check(tr_blocklistIsEnabled(session)); - - /* test blocked addresses */ - check(!address_is_blocked(session, "0.0.0.1")); - check(address_is_blocked(session, "10.1.2.3")); - check(!address_is_blocked(session, "216.16.1.143")); - check(address_is_blocked(session, "216.16.1.144")); - check(address_is_blocked(session, "216.16.1.145")); - check(address_is_blocked(session, "216.16.1.146")); - check(address_is_blocked(session, "216.16.1.147")); - check(address_is_blocked(session, "216.16.1.148")); - check(address_is_blocked(session, "216.16.1.149")); - check(address_is_blocked(session, "216.16.1.150")); - check(address_is_blocked(session, "216.16.1.151")); - check(!address_is_blocked(session, "216.16.1.152")); - check(!address_is_blocked(session, "216.16.1.153")); - check(!address_is_blocked(session, "217.0.0.1")); - check(!address_is_blocked(session, "255.0.0.1")); - - /* cleanup */ - libttest_session_close(session); - return 0; -} - -/*** -**** -***/ - -static int test_updating(void) -{ - char* path; - tr_session* session; - - /* init the session */ - session = libttest_session_init(NULL); - path = tr_buildPath(tr_sessionGetConfigDir(session), "blocklists", "level1", NULL); - - /* no blocklist to start with... */ - check_int(tr_blocklistGetRuleCount(session), ==, 0); - - /* test that updated source files will get loaded */ - create_text_file(path, contents1); - tr_sessionReloadBlocklists(session); - check_int(tr_blocklistGetRuleCount(session), ==, 5); - - /* test that updated source files will get loaded */ - create_text_file(path, contents2); - tr_sessionReloadBlocklists(session); - check_int(tr_blocklistGetRuleCount(session), ==, 6); - - /* test that updated source files will get loaded */ - create_text_file(path, contents1); - tr_sessionReloadBlocklists(session); - check_int(tr_blocklistGetRuleCount(session), ==, 5); - - /* ensure that new files, if bad, get skipped */ - create_text_file(path, "# nothing useful\n"); - tr_sessionReloadBlocklists(session); - check_int(tr_blocklistGetRuleCount(session), ==, 5); - - /* cleanup */ - libttest_session_close(session); - tr_free(path); - return 0; -} - -/*** -**** -***/ - -int main(void) -{ - testFunc const tests[] = - { - test_parsing, - test_updating - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/blocklist.h b/libtransmission/blocklist.h index 1a69c4cef..40fb36526 100644 --- a/libtransmission/blocklist.h +++ b/libtransmission/blocklist.h @@ -12,6 +12,10 @@ #error only libtransmission should #include this header. #endif +#include "tr-macros.h" + +TR_BEGIN_DECLS + struct tr_address; typedef struct tr_blocklistFile tr_blocklistFile; @@ -33,3 +37,5 @@ void tr_blocklistFileSetEnabled(tr_blocklistFile* b, bool isEnabled); bool tr_blocklistFileHasAddress(tr_blocklistFile* b, struct tr_address const* addr); int tr_blocklistFileSetContent(tr_blocklistFile* b, char const* filename); + +TR_END_DECLS diff --git a/libtransmission/cache.h b/libtransmission/cache.h index 7cceedcb9..c91e4c9ef 100644 --- a/libtransmission/cache.h +++ b/libtransmission/cache.h @@ -12,6 +12,10 @@ #error only libtransmission should #include this header. #endif +#include "tr-macros.h" + +TR_BEGIN_DECLS + struct evbuffer; typedef struct tr_cache tr_cache; @@ -49,3 +53,5 @@ int tr_cacheFlushDone(tr_cache* cache); int tr_cacheFlushTorrent(tr_cache* cache, tr_torrent* torrent); int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t file); + +TR_END_DECLS diff --git a/libtransmission/clients-test.c b/libtransmission/clients-test.c deleted file mode 100644 index 5fa34bfed..000000000 --- a/libtransmission/clients-test.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include "transmission.h" -#include "clients.h" - -#include "libtransmission-test.h" - -#define TEST_CLIENT(A, B) \ - tr_clientForId(buf, sizeof(buf), A); \ - check_str(buf, ==, B); - -int main(void) -{ - char buf[128]; - - TEST_CLIENT("-FC1013-", "FileCroc 1.0.1.3"); - TEST_CLIENT("-MR1100-", "Miro 1.1.0.0"); - TEST_CLIENT("-TR0006-", "Transmission 0.6"); - TEST_CLIENT("-TR0072-", "Transmission 0.72"); - TEST_CLIENT("-TR111Z-", "Transmission 1.11+"); - TEST_CLIENT("O1008132", "Osprey 1.0.0"); - TEST_CLIENT("TIX0193-", "Tixati 1.93"); - TEST_CLIENT("-UT341\0-", "\xc2\xb5Torrent 3.4.1"); - TEST_CLIENT("-BT791\0-", "BitTorrent 7.9.1"); - TEST_CLIENT("-BT791B-", "BitTorrent 7.9.1 (Beta)"); - - /* Xfplay 9.9.92 to 9.9.94 uses "-XF9992-" */ - TEST_CLIENT("-XF9992-", "Xfplay 9.9.92"); - - /* Older Xfplay versions have three digit version number */ - TEST_CLIENT("-XF9990-", "Xfplay 9.9.9"); - - /* PicoTorrent */ - TEST_CLIENT("-PI0091-", "PicoTorrent 0.09.1"); - TEST_CLIENT("-PI0120-", "PicoTorrent 0.12.0"); - - /* Free Download Manager */ - TEST_CLIENT("-FD51R\xFF-", "Free Download Manager 5.1.27"); - TEST_CLIENT("-FD51W\xFF-", "Free Download Manager 5.1.32"); - TEST_CLIENT("-FD51@\xFF-", "Free Download Manager 5.1.x"); /* Negative test case */ - - /* Folx */ - TEST_CLIENT("-FL51FF-", "Folx 5.x"); /* Folx v5.2.1.13690 */ - - /* Baidu Netdisk */ - TEST_CLIENT("-BN0001-", "Baidu Netdisk"); /* Baidu Netdisk Client v5.5.4 */ - - /* gobbledygook */ - TEST_CLIENT("-IIO\x10\x2D\x04-", "-IIO%10-%04-"); - TEST_CLIENT("-I\05O\x08\x03\x01-", "-I%05O%08%03%01-"); - - TEST_CLIENT("\x65\x78\x62\x63\x00\x38\x7A\x44\x63\x10\x2D\x6E\x9A\xD6\x72\x3B\x33\x9F\x35\xA9", "BitComet 0.56"); - TEST_CLIENT("\x65\x78\x62\x63\x00\x38\x4C\x4F\x52\x44\x32\x00\x04\x8E\xCE\xD5\x7B\xD7\x10\x28", "BitLord 0.56"); - - /* cleanup */ - return 0; -} diff --git a/libtransmission/clients.h b/libtransmission/clients.h index ef85a7486..096b0c646 100644 --- a/libtransmission/clients.h +++ b/libtransmission/clients.h @@ -12,8 +12,14 @@ #error only libtransmission should #include this header. #endif +#include "tr-macros.h" + +TR_BEGIN_DECLS + /** * @brief parse a peer-id into a human-readable client name and version number * @ingroup utils */ char* tr_clientForId(char* buf, size_t buflen, void const* peer_id); + +TR_END_DECLS diff --git a/libtransmission/crypto-test.c b/libtransmission/crypto-test.c deleted file mode 100644 index b6eecd1f8..000000000 --- a/libtransmission/crypto-test.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include - -#include "transmission.h" -#include "crypto.h" -#include "crypto-utils.h" -#include "utils.h" - -#include "libtransmission-test.h" - -#include "crypto-test-ref.h" - -static int test_torrent_hash(void) -{ - tr_crypto a; - uint8_t hash[SHA_DIGEST_LENGTH]; - - for (uint8_t i = 0; i < SHA_DIGEST_LENGTH; ++i) - { - hash[i] = i; - } - - tr_cryptoConstruct(&a, NULL, true); - - check(!tr_cryptoHasTorrentHash(&a)); - check_ptr(tr_cryptoGetTorrentHash(&a), ==, NULL); - - tr_cryptoSetTorrentHash(&a, hash); - check(tr_cryptoHasTorrentHash(&a)); - check_ptr(tr_cryptoGetTorrentHash(&a), !=, NULL); - check_mem(tr_cryptoGetTorrentHash(&a), ==, hash, SHA_DIGEST_LENGTH); - - tr_cryptoDestruct(&a); - - for (uint8_t i = 0; i < SHA_DIGEST_LENGTH; ++i) - { - hash[i] = i + 1; - } - - tr_cryptoConstruct(&a, hash, false); - - check(tr_cryptoHasTorrentHash(&a)); - check_ptr(tr_cryptoGetTorrentHash(&a), !=, NULL); - check_mem(tr_cryptoGetTorrentHash(&a), ==, hash, SHA_DIGEST_LENGTH); - - tr_cryptoSetTorrentHash(&a, NULL); - check(!tr_cryptoHasTorrentHash(&a)); - check_ptr(tr_cryptoGetTorrentHash(&a), ==, NULL); - - tr_cryptoDestruct(&a); - - return 0; -} - -static int test_encrypt_decrypt(void) -{ - tr_crypto a; - tr_crypto_ b; - uint8_t hash[SHA_DIGEST_LENGTH]; - char const test1[] = { "test1" }; - char buf11[sizeof(test1)]; - char buf12[sizeof(test1)]; - char const test2[] = { "@#)C$@)#(*%bvkdjfhwbc039bc4603756VB3)" }; - char buf21[sizeof(test2)]; - char buf22[sizeof(test2)]; - int public_key_length; - - for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) - { - hash[i] = (uint8_t)i; - } - - tr_cryptoConstruct(&a, hash, false); - tr_cryptoConstruct_(&b, hash, true); - check(tr_cryptoComputeSecret(&a, tr_cryptoGetMyPublicKey_(&b, &public_key_length))); - check(tr_cryptoComputeSecret_(&b, tr_cryptoGetMyPublicKey(&a, &public_key_length))); - - tr_cryptoEncryptInit(&a); - tr_cryptoEncrypt(&a, sizeof(test1), test1, buf11); - tr_cryptoDecryptInit_(&b); - tr_cryptoDecrypt_(&b, sizeof(test1), buf11, buf12); - check_str(buf12, ==, test1); - - tr_cryptoEncryptInit_(&b); - tr_cryptoEncrypt_(&b, sizeof(test2), test2, buf21); - tr_cryptoDecryptInit(&a); - tr_cryptoDecrypt(&a, sizeof(test2), buf21, buf22); - check_str(buf22, ==, test2); - - tr_cryptoDestruct_(&b); - tr_cryptoDestruct(&a); - - return 0; -} - -static int test_sha1(void) -{ - uint8_t hash[SHA_DIGEST_LENGTH]; - uint8_t hash_[SHA_DIGEST_LENGTH]; - - check(tr_sha1(hash, "test", 4, NULL)); - check(tr_sha1_(hash_, "test", 4, NULL)); - check_mem(hash, ==, "\xa9\x4a\x8f\xe5\xcc\xb1\x9b\xa6\x1c\x4c\x08\x73\xd3\x91\xe9\x87\x98\x2f\xbb\xd3", SHA_DIGEST_LENGTH); - check_mem(hash, ==, hash_, SHA_DIGEST_LENGTH); - - check(tr_sha1(hash, "1", 1, "22", 2, "333", 3, NULL)); - check(tr_sha1_(hash_, "1", 1, "22", 2, "333", 3, NULL)); - check_mem(hash, ==, "\x1f\x74\x64\x8e\x50\xa6\xa6\x70\x8e\xc5\x4a\xb3\x27\xa1\x63\xd5\x53\x6b\x7c\xed", SHA_DIGEST_LENGTH); - check_mem(hash, ==, hash_, SHA_DIGEST_LENGTH); - - return 0; -} - -static int test_ssha1(void) -{ - struct - { - char const* const plain_text; - char const* const ssha1; - } - test_data[] = - { - { "test", "{15ad0621b259a84d24dcd4e75b09004e98a3627bAMbyRHJy" }, - { "QNY)(*#$B)!_X$B !_B#($^!)*&$%CV!#)&$C!@$(P*)", "{10e2d7acbb104d970514a147cd16d51dfa40fb3c0OSwJtOL" } - }; - -#define HASH_COUNT (4 * 1024) - - for (size_t i = 0; i < TR_N_ELEMENTS(test_data); ++i) - { - char* const phrase = tr_strdup(test_data[i].plain_text); - char** hashes = tr_new(char*, HASH_COUNT); - - check(tr_ssha1_matches(test_data[i].ssha1, phrase)); - check(tr_ssha1_matches_(test_data[i].ssha1, phrase)); - - for (size_t j = 0; j < HASH_COUNT; ++j) - { - hashes[j] = j % 2 == 0 ? tr_ssha1(phrase) : tr_ssha1_(phrase); - - check_ptr(hashes[j], !=, NULL); - - /* phrase matches each of generated hashes */ - check(tr_ssha1_matches(hashes[j], phrase)); - check(tr_ssha1_matches_(hashes[j], phrase)); - } - - for (size_t j = 0; j < HASH_COUNT; ++j) - { - /* all hashes are different */ - for (size_t k = 0; k < HASH_COUNT; ++k) - { - if (k != j) - { - check_str(hashes[j], !=, hashes[k]); - } - } - } - - /* exchange two first chars */ - phrase[0] ^= phrase[1]; - phrase[1] ^= phrase[0]; - phrase[0] ^= phrase[1]; - - for (size_t j = 0; j < HASH_COUNT; ++j) - { - /* changed phrase doesn't match the hashes */ - check(!tr_ssha1_matches(hashes[j], phrase)); - check(!tr_ssha1_matches_(hashes[j], phrase)); - } - - for (size_t j = 0; j < HASH_COUNT; ++j) - { - tr_free(hashes[j]); - } - - tr_free(hashes); - tr_free(phrase); - } - -#undef HASH_COUNT - - /* should work with different salt lengths as well */ - check(tr_ssha1_matches("{a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "test")); - check(tr_ssha1_matches("{d209a21d3bc4f8fc4f8faf347e69f3def597eb170pySy4ai1ZPMjeU1", "test")); - - return 0; -} - -static int test_random(void) -{ - /* test that tr_rand_int() stays in-bounds */ - for (int i = 0; i < 100000; ++i) - { - int const val = tr_rand_int(100); - check_int(val, >=, 0); - check_int(val, <, 100); - } - - return 0; -} - -static bool base64_eq(char const* a, char const* b) -{ - for (;; ++a, ++b) - { - while (*a == '\r' || *a == '\n') - { - ++a; - } - - while (*b == '\r' || *b == '\n') - { - ++b; - } - - if (*a == '\0' || *b == '\0' || *a != *b) - { - break; - } - } - - return *a == *b; -} - -static int test_base64(void) -{ - size_t len; - char* in; - char* out; - - out = tr_base64_encode_str("YOYO!", &len); - check_uint(len, ==, strlen(out)); - check(base64_eq("WU9ZTyE=", out)); - in = tr_base64_decode_str(out, &len); - check_uint(len, ==, 5); - check_str(in, ==, "YOYO!"); - tr_free(in); - tr_free(out); - - out = tr_base64_encode("", 0, &len); - check_uint(len, ==, 0); - check_str(out, ==, ""); - tr_free(out); - out = tr_base64_decode("", 0, &len); - check_uint(len, ==, 0); - check_str(out, ==, ""); - tr_free(out); - - out = tr_base64_encode(NULL, 0, &len); - check_uint(len, ==, 0); - check_str(out, ==, NULL); - out = tr_base64_decode(NULL, 0, &len); - check_uint(len, ==, 0); - check_str(out, ==, NULL); - -#define MAX_BUF_SIZE 1024 - - for (size_t i = 1; i <= MAX_BUF_SIZE; ++i) - { - char buf[MAX_BUF_SIZE + 1]; - - for (size_t j = 0; j < i; ++j) - { - buf[j] = (char)tr_rand_int_weak(256); - } - - out = tr_base64_encode(buf, i, &len); - check_uint(len, ==, strlen(out)); - in = tr_base64_decode(out, len, &len); - check_uint(len, ==, i); - check_mem(in, ==, buf, len); - tr_free(in); - tr_free(out); - - for (size_t j = 0; j < i; ++j) - { - buf[j] = (char)(1 + tr_rand_int_weak(255)); - } - - buf[i] = '\0'; - - out = tr_base64_encode_str(buf, &len); - check_uint(len, ==, strlen(out)); - in = tr_base64_decode_str(out, &len); - check_uint(len, ==, i); - check_str(buf, ==, in); - tr_free(in); - tr_free(out); - } - -#undef MAX_BUF_SIZE - - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - test_torrent_hash, - test_encrypt_decrypt, - test_sha1, - test_ssha1, - test_random, - test_base64 - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/crypto-utils-openssl.c b/libtransmission/crypto-utils-openssl.c index 45fd71913..ea58af44d 100644 --- a/libtransmission/crypto-utils-openssl.c +++ b/libtransmission/crypto-utils-openssl.c @@ -81,7 +81,6 @@ static bool check_openssl_result(int result, int expected_result, bool expected_ } #define check_result(result) check_openssl_result((result), 1, true, __FILE__, __LINE__) -#define check_result_eq(result, x_result) check_openssl_result((result), (x_result), true, __FILE__, __LINE__) #define check_result_neq(result, x_result) check_openssl_result((result), (x_result), false, __FILE__, __LINE__) static bool check_openssl_pointer(void* pointer, char const* file, int line) diff --git a/libtransmission/crypto-utils.h b/libtransmission/crypto-utils.h index 7ae3cee86..3d7c9496d 100644 --- a/libtransmission/crypto-utils.h +++ b/libtransmission/crypto-utils.h @@ -13,12 +13,10 @@ #include #include "transmission.h" /* SHA_DIGEST_LENGTH */ +#include "tr-macros.h" #include "utils.h" /* TR_GNUC_MALLOC, TR_GNUC_NULL_TERMINATED */ -#ifdef __cplusplus -extern "C" -{ -#endif +TR_BEGIN_DECLS /** *** @addtogroup utils Utilities @@ -193,7 +191,7 @@ void* tr_base64_decode_str(char const* input, size_t* output_length) TR_GNUC_MAL /** * @brief Wrapper around tr_binary_to_hex() for SHA_DIGEST_LENGTH. */ -static inline void tr_sha1_to_hex(char* hex, uint8_t const* sha1) +static inline void tr_sha1_to_hex(void* hex, void const* sha1) { tr_binary_to_hex(sha1, hex, SHA_DIGEST_LENGTH); } @@ -201,15 +199,13 @@ static inline void tr_sha1_to_hex(char* hex, uint8_t const* sha1) /** * @brief Wrapper around tr_hex_to_binary() for SHA_DIGEST_LENGTH. */ -static inline void tr_hex_to_sha1(uint8_t* sha1, char const* hex) +static inline void tr_hex_to_sha1(void* sha1, void const* hex) { tr_hex_to_binary(hex, sha1, SHA_DIGEST_LENGTH); } /** @} */ -#ifdef __cplusplus -} -#endif +TR_END_DECLS #endif /* TR_CRYPTO_UTILS_H */ diff --git a/libtransmission/crypto.h b/libtransmission/crypto.h index 7e6c85d42..22a4775f3 100644 --- a/libtransmission/crypto.h +++ b/libtransmission/crypto.h @@ -6,6 +6,7 @@ * */ +// NB: crypto-test-ref.h needs this, so use it instead of #pragma once #ifndef TR_ENCRYPTION_H #define TR_ENCRYPTION_H @@ -16,8 +17,11 @@ #include #include "crypto-utils.h" +#include "tr-macros.h" #include "utils.h" /* TR_GNUC_NULL_TERMINATED */ +TR_BEGIN_DECLS + /** *** @addtogroup peers *** @{ @@ -71,4 +75,6 @@ bool tr_cryptoSecretKeySha1(tr_crypto const* crypto, void const* prepend_data, s /* @} */ -#endif +TR_END_DECLS + +#endif // TR_ENCRYPTION_H diff --git a/libtransmission/error-test.c b/libtransmission/error-test.c deleted file mode 100644 index 816443f74..000000000 --- a/libtransmission/error-test.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include "transmission.h" -#include "error.h" - -#include "libtransmission-test.h" - -static int test_error_set(void) -{ - tr_error* err = NULL; - - tr_error_prefix(&err, "error: "); - check_ptr(err, ==, NULL); - - tr_error_set(&err, 1, "error: %s (%d)", "oops", 2); - check(err != NULL); - check_int(err->code, ==, 1); - check_str(err->message, ==, "error: oops (2)"); - tr_error_clear(&err); - check_ptr(err, ==, NULL); - - tr_error_set_literal(&err, 2, "oops"); - check_ptr(err, !=, NULL); - check_int(err->code, ==, 2); - check_str(err->message, ==, "oops"); - - tr_error_prefix(&err, "error: "); - check_ptr(err, !=, NULL); - check_int(err->code, ==, 2); - check_str(err->message, ==, "error: oops"); - - tr_error_free(err); - - return 0; -} - -static int test_error_propagate(void) -{ - tr_error* err = NULL; - tr_error* err2 = NULL; - - tr_error_set_literal(&err, 1, "oops"); - check_ptr(err, !=, NULL); - check_int(err->code, ==, 1); - check_str(err->message, ==, "oops"); - - tr_error_propagate(&err2, &err); - check_ptr(err2, !=, NULL); - check_int(err2->code, ==, 1); - check_str(err2->message, ==, "oops"); - check_ptr(err, ==, NULL); - - tr_error_propagate_prefixed(&err, &err2, "error: "); - check_ptr(err, !=, NULL); - check_int(err->code, ==, 1); - check_str(err->message, ==, "error: oops"); - check_ptr(err2, ==, NULL); - - tr_error_propagate(NULL, &err); - check_ptr(err, ==, NULL); - - tr_error_free(err2); - - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - test_error_set, - test_error_propagate - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/error.h b/libtransmission/error.h index 0e96f06bc..3bfc07d71 100644 --- a/libtransmission/error.h +++ b/libtransmission/error.h @@ -12,10 +12,7 @@ #include "tr-macros.h" -#ifdef __cplusplus -extern "C" -{ -#endif +TR_BEGIN_DECLS /** * @addtogroup error Error reporting @@ -134,6 +131,4 @@ void tr_error_propagate_prefixed(tr_error** new_error, tr_error** old_error, cha /** @} */ -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/fail.sh b/libtransmission/fail.sh deleted file mode 100755 index d0beda970..000000000 --- a/libtransmission/fail.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -err=0 -count=0 -while [ $err -eq 0 ]; do - count=$((count + 1)) - echo starting run number $count - make check - err=$? -done diff --git a/libtransmission/file-posix.c b/libtransmission/file-posix.c index 55853eac5..6b2f3c885 100644 --- a/libtransmission/file-posix.c +++ b/libtransmission/file-posix.c @@ -844,6 +844,8 @@ skip_darwin_fcntl: bool tr_sys_file_preallocate(tr_sys_file_t handle, uint64_t size, int flags, tr_error** error) { + (void)size; + TR_ASSERT(handle != TR_BAD_SYS_FILE); bool ret = false; diff --git a/libtransmission/file-test.c b/libtransmission/file-test.c deleted file mode 100644 index 32e9a180e..000000000 --- a/libtransmission/file-test.c +++ /dev/null @@ -1,1652 +0,0 @@ -/* - * 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 - -#ifndef _WIN32 -#include -#include -#include -#else -#include -#endif - -#include "transmission.h" -#include "error.h" -#include "file.h" - -#include "libtransmission-test.h" - -#ifndef _WIN32 -#define NATIVE_PATH_SEP "/" -#else -#define NATIVE_PATH_SEP "\\" -#endif - -#if !defined(__OpenBSD__) -#define HAVE_UNIFIED_BUFFER_CACHE -#endif - -static tr_session* session; - -static char* create_test_dir(char const* name) -{ - char* const test_dir = tr_buildPath(tr_sessionGetConfigDir(session), name, NULL); - tr_sys_dir_create(test_dir, 0, 0777, NULL); - return test_dir; -} - -static bool create_symlink(char const* dst_path, char const* src_path, bool dst_is_dir) -{ -#ifndef _WIN32 - - (void)dst_is_dir; - - return symlink(src_path, dst_path) != -1; - -#else - - wchar_t* wide_src_path; - wchar_t* wide_dst_path; - bool ret = false; - - wide_src_path = tr_win32_utf8_to_native(src_path, -1); - wide_dst_path = tr_win32_utf8_to_native(dst_path, -1); - - ret = CreateSymbolicLinkW(wide_dst_path, wide_src_path, dst_is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0); - - tr_free(wide_dst_path); - tr_free(wide_src_path); - - return ret; - -#endif -} - -static bool create_hardlink(char const* dst_path, char const* src_path) -{ -#ifndef _WIN32 - - return link(src_path, dst_path) != -1; - -#else - - wchar_t* wide_src_path = tr_win32_utf8_to_native(src_path, -1); - wchar_t* wide_dst_path = tr_win32_utf8_to_native(dst_path, -1); - - bool ret = CreateHardLinkW(wide_dst_path, wide_src_path, NULL); - - tr_free(wide_dst_path); - tr_free(wide_src_path); - - return ret; - -#endif -} - -static void clear_path_info(tr_sys_path_info* info) -{ - info->type = (tr_sys_path_type_t)-1; - info->size = (uint64_t)-1; - info->last_modified_at = (time_t)-1; -} - -static bool path_contains_no_symlinks(char const* path) -{ - char const* p = path; - - while (*p != '\0') - { - tr_sys_path_info info; - char* pathPart; - char const* slashPos = strchr(p, '/'); - -#ifdef _WIN32 - - char const* backslashPos = strchr(p, '\\'); - - if (slashPos == NULL || (backslashPos != NULL && backslashPos < slashPos)) - { - slashPos = backslashPos; - } - -#endif - - if (slashPos == NULL) - { - slashPos = p + strlen(p) - 1; - } - - pathPart = tr_strndup(path, (size_t)(slashPos - path + 1)); - - if (!tr_sys_path_get_info(pathPart, TR_SYS_PATH_NO_FOLLOW, &info, NULL) || - (info.type != TR_SYS_PATH_IS_FILE && info.type != TR_SYS_PATH_IS_DIRECTORY)) - { - tr_free(pathPart); - return false; - } - - tr_free(pathPart); - - p = slashPos + 1; - } - - return true; -} - -static bool validate_permissions(char const* path, unsigned int permissions) -{ -#ifndef _WIN32 - - struct stat sb; - return stat(path, &sb) != -1 && (sb.st_mode & 0777) == permissions; - -#else - - (void)path; - (void)permissions; - - /* No UNIX permissions on Windows */ - return true; - -#endif -} - -static int test_get_info(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_sys_path_info info; - tr_sys_file_t fd; - tr_error* err = NULL; - char* path1; - char* path2; - time_t t; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - - /* Can't get info of non-existent file/directory */ - check(!tr_sys_path_get_info(path1, 0, &info, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - t = time(NULL); - libtest_create_file_with_string_contents(path1, "test"); - - /* Good file info */ - clear_path_info(&info); - check(tr_sys_path_get_info(path1, 0, &info, &err)); - check_ptr(err, ==, NULL); - check_int(info.type, ==, TR_SYS_PATH_IS_FILE); - check_uint(info.size, ==, 4); - check_int(info.last_modified_at, >=, t - 1); - check_int(info.last_modified_at, <=, time(NULL) + 1); - - /* Good file info (by handle) */ - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0, NULL); - clear_path_info(&info); - check(tr_sys_file_get_info(fd, &info, &err)); - check_ptr(err, ==, NULL); - check_int(info.type, ==, TR_SYS_PATH_IS_FILE); - check_uint(info.size, ==, 4); - check_int(info.last_modified_at, >=, t - 1); - check_int(info.last_modified_at, <=, time(NULL) + 1); - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - - /* Good directory info */ - t = time(NULL); - tr_sys_dir_create(path1, 0, 0777, NULL); - clear_path_info(&info); - check(tr_sys_path_get_info(path1, 0, &info, &err)); - check_ptr(err, ==, NULL); - check_int(info.type, ==, TR_SYS_PATH_IS_DIRECTORY); - check_uint(info.size, !=, (uint64_t)-1); - check_int(info.last_modified_at, >=, t - 1); - check_int(info.last_modified_at, <=, time(NULL) + 1); - tr_sys_path_remove(path1, NULL); - - if (create_symlink(path1, path2, false)) - { - /* Can't get info of non-existent file/directory */ - check(!tr_sys_path_get_info(path1, 0, &info, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - t = time(NULL); - libtest_create_file_with_string_contents(path2, "test"); - - /* Good file info */ - clear_path_info(&info); - check(tr_sys_path_get_info(path1, 0, &info, &err)); - check_ptr(err, ==, NULL); - check_int(info.type, ==, TR_SYS_PATH_IS_FILE); - check_uint(info.size, ==, 4); - check_int(info.last_modified_at, >=, t - 1); - check_int(info.last_modified_at, <=, time(NULL) + 1); - - /* Good file info (by handle) */ - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0, NULL); - clear_path_info(&info); - check(tr_sys_file_get_info(fd, &info, &err)); - check_ptr(err, ==, NULL); - check_int(info.type, ==, TR_SYS_PATH_IS_FILE); - check_uint(info.size, ==, 4); - check_int(info.last_modified_at, >=, t - 1); - check_int(info.last_modified_at, <=, time(NULL) + 1); - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - - /* Good directory info */ - t = time(NULL); - tr_sys_dir_create(path2, 0, 0777, NULL); - check(create_symlink(path1, path2, true)); /* Win32: directory and file symlinks differ :( */ - clear_path_info(&info); - check(tr_sys_path_get_info(path1, 0, &info, &err)); - check_ptr(err, ==, NULL); - check_int(info.type, ==, TR_SYS_PATH_IS_DIRECTORY); - check_uint(info.size, !=, (uint64_t)-1); - check_int(info.last_modified_at, >=, t - 1); - check_int(info.last_modified_at, <=, time(NULL) + 1); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); - } - - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_path_exists(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - char* path2; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - - /* Non-existent file does not exist */ - check(!tr_sys_path_exists(path1, &err)); - check_ptr(err, ==, NULL); - - /* Create file and see that it exists */ - libtest_create_file_with_string_contents(path1, "test"); - check(tr_sys_path_exists(path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path1, NULL); - - /* Create directory and see that it exists */ - tr_sys_dir_create(path1, 0, 0777, NULL); - check(tr_sys_path_exists(path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path1, NULL); - - if (create_symlink(path1, path2, false)) - { - /* Non-existent file does not exist (via symlink) */ - check(!tr_sys_path_exists(path1, &err)); - check_ptr(err, ==, NULL); - - /* Create file and see that it exists (via symlink) */ - libtest_create_file_with_string_contents(path2, "test"); - check(tr_sys_path_exists(path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - - /* Create directory and see that it exists (via symlink) */ - tr_sys_dir_create(path2, 0, 0777, NULL); - check(create_symlink(path1, path2, true)); /* Win32: directory and file symlinks differ :( */ - check(tr_sys_path_exists(path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); - } - - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_path_is_relative(void) -{ - check(tr_sys_path_is_relative("")); - check(tr_sys_path_is_relative(".")); - check(tr_sys_path_is_relative("..")); - check(tr_sys_path_is_relative("x")); - check(tr_sys_path_is_relative("\\")); - check(tr_sys_path_is_relative(":")); - -#ifdef _WIN32 - - check(tr_sys_path_is_relative("/")); - check(tr_sys_path_is_relative("\\x")); - check(tr_sys_path_is_relative("/x")); - check(tr_sys_path_is_relative("\\x\\y")); - check(tr_sys_path_is_relative("/x/y")); - check(tr_sys_path_is_relative("C:x")); - check(tr_sys_path_is_relative("C:x\\y")); - check(tr_sys_path_is_relative("C:x/y")); - - check(!tr_sys_path_is_relative("\\\\")); - check(!tr_sys_path_is_relative("//")); - check(!tr_sys_path_is_relative("\\\\x")); - check(!tr_sys_path_is_relative("//x")); - check(!tr_sys_path_is_relative("\\\\x\\y")); - check(!tr_sys_path_is_relative("//x/y")); - check(!tr_sys_path_is_relative("\\\\.\\x")); - check(!tr_sys_path_is_relative("//./x")); - - check(!tr_sys_path_is_relative("a:")); - check(!tr_sys_path_is_relative("a:\\")); - check(!tr_sys_path_is_relative("a:/")); - check(!tr_sys_path_is_relative("Z:")); - check(!tr_sys_path_is_relative("Z:\\")); - check(!tr_sys_path_is_relative("Z:/")); - -#else /* _WIN32 */ - - check(!tr_sys_path_is_relative("/")); - check(!tr_sys_path_is_relative("/x")); - check(!tr_sys_path_is_relative("/x/y")); - check(!tr_sys_path_is_relative("//x")); - -#endif /* _WIN32 */ - - return 0; -} - -static int test_path_is_same(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - char* path2; - char* path3; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - path3 = tr_buildPath(path2, "c", NULL); - - /* Two non-existent files are not the same */ - check(!tr_sys_path_is_same(path1, path1, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - - /* Two same files are the same */ - libtest_create_file_with_string_contents(path1, "test"); - check(tr_sys_path_is_same(path1, path1, &err)); - check_ptr(err, ==, NULL); - - /* Existent and non-existent files are not the same */ - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path2, path1, &err)); - check_ptr(err, ==, NULL); - - /* Two separate files (even with same content) are not the same */ - libtest_create_file_with_string_contents(path2, "test"); - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path1, NULL); - - /* Two same directories are the same */ - tr_sys_dir_create(path1, 0, 0777, NULL); - check(tr_sys_path_is_same(path1, path1, &err)); - check_ptr(err, ==, NULL); - - /* File and directory are not the same */ - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path2, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - - /* Two separate directories are not the same */ - tr_sys_dir_create(path2, 0, 0777, NULL); - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path1, NULL); - tr_sys_path_remove(path2, NULL); - - if (create_symlink(path1, ".", true)) - { - /* Directory and symlink pointing to it are the same */ - check(tr_sys_path_is_same(path1, test_dir, &err)); - check_ptr(err, ==, NULL); - check(tr_sys_path_is_same(test_dir, path1, &err)); - check_ptr(err, ==, NULL); - - /* Non-existent file and symlink are not the same */ - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path2, path1, &err)); - check_ptr(err, ==, NULL); - - /* Symlinks pointing to different directories are not the same */ - create_symlink(path2, "..", true); - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path2, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - - /* Symlinks pointing to same directory are the same */ - create_symlink(path2, ".", true); - check(tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - - /* Directory and symlink pointing to another directory are not the same */ - tr_sys_dir_create(path2, 0, 0777, NULL); - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path2, path1, &err)); - check_ptr(err, ==, NULL); - - /* Symlinks pointing to same directory are the same */ - create_symlink(path3, "..", true); - check(tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path1, NULL); - - /* File and symlink pointing to directory are not the same */ - libtest_create_file_with_string_contents(path1, "test"); - check(!tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path3, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path3, NULL); - - /* File and symlink pointing to same file are the same */ - create_symlink(path3, path1, false); - check(tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - check(tr_sys_path_is_same(path3, path1, &err)); - check_ptr(err, ==, NULL); - - /* Symlinks pointing to non-existent files are not the same */ - tr_sys_path_remove(path1, NULL); - create_symlink(path1, "missing", false); - tr_sys_path_remove(path3, NULL); - create_symlink(path3, "missing", false); - check(!tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path3, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path3, NULL); - - /* Symlinks pointing to same non-existent file are not the same */ - create_symlink(path3, ".." NATIVE_PATH_SEP "missing", false); - check(!tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path3, path1, &err)); - check_ptr(err, ==, NULL); - - /* Non-existent file and symlink pointing to non-existent file are not the same */ - tr_sys_path_remove(path3, NULL); - check(!tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path3, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); - } - - tr_free(path3); - path3 = tr_buildPath(test_dir, "c", NULL); - - libtest_create_file_with_string_contents(path1, "test"); - - if (create_hardlink(path2, path1)) - { - /* File and hardlink to it are the same */ - check(tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - - /* Two hardlinks to the same file are the same */ - create_hardlink(path3, path2); - check(tr_sys_path_is_same(path2, path3, &err)); - check_ptr(err, ==, NULL); - check(tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - - check(tr_sys_path_is_same(path1, path3, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path3, NULL); - - /* File and hardlink to another file are not the same */ - libtest_create_file_with_string_contents(path3, "test"); - create_hardlink(path2, path3); - check(!tr_sys_path_is_same(path1, path2, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_is_same(path2, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path3, NULL); - tr_sys_path_remove(path2, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run hardlink tests\n", __FUNCTION__); - } - - if (create_symlink(path2, path1, false) && create_hardlink(path3, path1)) - { - check(tr_sys_path_is_same(path2, path3, &err)); - check_ptr(err, ==, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run combined symlink and hardlink tests\n", __FUNCTION__); - } - - tr_sys_path_remove(path3, NULL); - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - - tr_free(path3); - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_path_resolve(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - char* path2; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - - libtest_create_file_with_string_contents(path1, "test"); - - if (create_symlink(path2, path1, false)) - { - char* tmp; - - tmp = tr_sys_path_resolve(path2, &err); - check_str(tmp, !=, NULL); - check_ptr(err, ==, NULL); - check(path_contains_no_symlinks(tmp)); - tr_free(tmp); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - - tr_sys_dir_create(path1, 0, 0755, NULL); - check(create_symlink(path2, path1, true)); /* Win32: directory and file symlinks differ :( */ - tmp = tr_sys_path_resolve(path2, &err); - check_str(tmp, !=, NULL); - check_ptr(err, ==, NULL); - check(path_contains_no_symlinks(tmp)); - tr_free(tmp); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); - } - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - - tr_free(path2); - tr_free(path1); - -#ifdef _WIN32 - - { - char* tmp; - - tmp = tr_sys_path_resolve("\\\\127.0.0.1\\NonExistent", &err); - check_str(tmp, ==, NULL); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - tmp = tr_sys_path_resolve("\\\\127.0.0.1\\ADMIN$\\NonExistent", &err); - check_str(tmp, ==, NULL); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - tmp = tr_sys_path_resolve("\\\\127.0.0.1\\ADMIN$\\System32", &err); - check_str(tmp, ==, "\\\\127.0.0.1\\ADMIN$\\System32"); - check_ptr(err, ==, NULL); - tr_free(tmp); - } - -#endif - - tr_free(test_dir); - return 0; -} - -struct xname_test_data -{ - char const* input; - char const* output; -}; - -static int test_path_xname(struct xname_test_data const* data, size_t data_size, char* (*func)(char const*, tr_error**)) -{ - for (size_t i = 0; i < data_size; ++i) - { - tr_error* err = NULL; - char* name = func(data[i].input, &err); - - if (data[i].output != NULL) - { - check_str(name, !=, NULL); - check_ptr(err, ==, NULL); - check_str(name, ==, data[i].output); - tr_free(name); - } - else - { - check_str(name, ==, NULL); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - } - } - - return 0; -} - -static int test_path_basename_dirname(void) -{ - struct xname_test_data const common_xname_tests[] = - { - { "/", "/" }, - { "", "." }, -#ifdef _WIN32 - { "\\", "/" }, - /* Invalid paths */ - { "\\\\\\", NULL }, - { "123:", NULL }, - /* Reserved characters */ - { "<", NULL }, - { ">", NULL }, - { ":", NULL }, - { "\"", NULL }, - { "|", NULL }, - { "?", NULL }, - { "*", NULL }, - { "a\\<", NULL }, - { "a\\>", NULL }, - { "a\\:", NULL }, - { "a\\\"", NULL }, - { "a\\|", NULL }, - { "a\\?", NULL }, - { "a\\*", NULL }, - { "c:\\a\\bc\\d", NULL }, - { "c:\\a\\b:c\\d", NULL }, - { "c:\\a\\b\"c\\d", NULL }, - { "c:\\a\\b|c\\d", NULL }, - { "c:\\a\\b?c\\d", NULL }, - { "c:\\a\\b*c\\d", NULL } -#else - { "////", "/" } -#endif - }; - - if (test_path_xname(common_xname_tests, TR_N_ELEMENTS(common_xname_tests), tr_sys_path_basename) != 0) - { - return 1; - } - - if (test_path_xname(common_xname_tests, TR_N_ELEMENTS(common_xname_tests), tr_sys_path_dirname) != 0) - { - return 1; - } - - struct xname_test_data const basename_tests[] = - { - { "a", "a" }, - { "aa", "aa" }, - { "/aa", "aa" }, - { "/a/b/c", "c" }, - { "/a/b/c/", "c" }, -#ifdef _WIN32 - { "c:\\a\\b\\c", "c" }, - { "c:", "/" }, - { "c:/", "/" }, - { "c:\\", "/" }, - { "c:a/b", "b" }, - { "c:a", "a" }, - { "\\\\a\\b\\c", "c" }, - { "//a/b", "b" }, - { "//1.2.3.4/b", "b" }, - { "\\\\a", "a" }, - { "\\\\1.2.3.4", "1.2.3.4" }, - { "\\", "/" }, - { "\\a", "a" } -#endif - }; - - if (test_path_xname(basename_tests, TR_N_ELEMENTS(basename_tests), tr_sys_path_basename) != 0) - { - return 1; - } - - struct xname_test_data const dirname_tests[] = - { - { "/a/b/c", "/a/b" }, - { "a/b/c", "a/b" }, - { "a/b/c/", "a/b" }, - { "a", "." }, - { "a/", "." }, -#ifdef _WIN32 - { "C:\\a/b\\c", "C:\\a/b" }, - { "C:\\a/b\\c\\", "C:\\a/b" }, - { "C:\\a/b", "C:\\a" }, - { "C:/a", "C:" }, - { "C:", "C:" }, - { "C:/", "C:" }, - { "C:\\", "C:" }, - { "c:a/b", "c:a" }, - { "c:a", "c:." }, - { "c:.", "c:." }, - { "\\\\a\\b\\c", "\\\\a\\b" }, - { "\\\\a\\b\\c/", "\\\\a\\b" }, - { "//a/b", "//a" }, - { "//1.2.3.4/b", "//1.2.3.4" }, - { "\\\\a", "\\\\" }, - { "\\\\1.2.3.4", "\\\\" }, - { "\\\\", "\\\\" }, - { "a/b\\c", "a/b" } -#endif - }; - - if (test_path_xname(dirname_tests, TR_N_ELEMENTS(dirname_tests), tr_sys_path_dirname) != 0) - { - return 1; - } - - /* TODO: is_same(dirname(x) + '/' + basename(x), x) */ - - return 0; -} - -static int test_path_rename(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - char* path2; - char* path3; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - path3 = tr_buildPath(path2, "c", NULL); - - libtest_create_file_with_string_contents(path1, "test"); - - /* Preconditions */ - check(tr_sys_path_exists(path1, NULL)); - check(!tr_sys_path_exists(path2, NULL)); - - /* Forward rename works */ - check(tr_sys_path_rename(path1, path2, &err)); - check(!tr_sys_path_exists(path1, NULL)); - check(tr_sys_path_exists(path2, NULL)); - check_ptr(err, ==, NULL); - - /* Backward rename works */ - check(tr_sys_path_rename(path2, path1, &err)); - check(tr_sys_path_exists(path1, NULL)); - check(!tr_sys_path_exists(path2, NULL)); - check_ptr(err, ==, NULL); - - /* Another backward rename [of non-existent file] does not work */ - check(!tr_sys_path_rename(path2, path1, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - /* Rename to file which couldn't be created does not work */ - check(!tr_sys_path_rename(path1, path3, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - /* Rename of non-existent file does not work */ - check(!tr_sys_path_rename(path3, path2, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - libtest_create_file_with_string_contents(path2, "test"); - - /* Renaming file does overwrite existing file */ - check(tr_sys_path_rename(path2, path1, &err)); - check_ptr(err, ==, NULL); - - tr_sys_dir_create(path2, 0, 0777, NULL); - - /* Renaming file does not overwrite existing directory, and vice versa */ - check(!tr_sys_path_rename(path1, path2, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - check(!tr_sys_path_rename(path2, path1, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - tr_sys_path_remove(path2, NULL); - - tr_free(path3); - path3 = tr_buildPath(test_dir, "c", NULL); - - if (create_symlink(path2, path1, false)) - { - /* Preconditions */ - check(tr_sys_path_exists(path2, NULL)); - check(!tr_sys_path_exists(path3, NULL)); - check(tr_sys_path_is_same(path1, path2, NULL)); - - /* Rename of symlink works, files stay the same */ - check(tr_sys_path_rename(path2, path3, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_exists(path2, NULL)); - check(tr_sys_path_exists(path3, NULL)); - check(tr_sys_path_is_same(path1, path3, NULL)); - - tr_sys_path_remove(path3, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); - } - - if (create_hardlink(path2, path1)) - { - /* Preconditions */ - check(tr_sys_path_exists(path2, NULL)); - check(!tr_sys_path_exists(path3, NULL)); - check(tr_sys_path_is_same(path1, path2, NULL)); - - /* Rename of hardlink works, files stay the same */ - check(tr_sys_path_rename(path2, path3, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_exists(path2, NULL)); - check(tr_sys_path_exists(path3, NULL)); - check(tr_sys_path_is_same(path1, path3, NULL)); - - tr_sys_path_remove(path3, NULL); - } - else - { - fprintf(stderr, "WARNING: [%s] unable to run hardlink tests\n", __FUNCTION__); - } - - tr_sys_path_remove(path1, NULL); - - tr_free(path3); - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_path_remove(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - char* path2; - char* path3; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - path3 = tr_buildPath(path2, "c", NULL); - - /* Can't remove non-existent file/directory */ - check(!tr_sys_path_exists(path1, NULL)); - check(!tr_sys_path_remove(path1, &err)); - check_ptr(err, !=, NULL); - check(!tr_sys_path_exists(path1, NULL)); - tr_error_clear(&err); - - /* Removing file works */ - libtest_create_file_with_string_contents(path1, "test"); - check(tr_sys_path_exists(path1, NULL)); - check(tr_sys_path_remove(path1, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_exists(path1, NULL)); - - /* Removing empty directory works */ - tr_sys_dir_create(path1, 0, 0777, NULL); - check(tr_sys_path_exists(path1, NULL)); - check(tr_sys_path_remove(path1, &err)); - check_ptr(err, ==, NULL); - check(!tr_sys_path_exists(path1, NULL)); - - /* Removing non-empty directory fails */ - tr_sys_dir_create(path2, 0, 0777, NULL); - libtest_create_file_with_string_contents(path3, "test"); - check(tr_sys_path_exists(path2, NULL)); - check(tr_sys_path_exists(path3, NULL)); - check(!tr_sys_path_remove(path2, &err)); - check_ptr(err, !=, NULL); - check(tr_sys_path_exists(path2, NULL)); - check(tr_sys_path_exists(path3, NULL)); - tr_error_clear(&err); - - tr_sys_path_remove(path3, NULL); - tr_sys_path_remove(path2, NULL); - - tr_free(path3); - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_path_native_separators(void) -{ - check_str(tr_sys_path_native_separators(NULL), ==, NULL); - - char path1[] = ""; - char path2[] = "a"; - char path3[] = "/"; - char path4[] = "/a/b/c"; - char path5[] = "C:\\a/b\\c"; - - struct - { - char* input; - char const* output; - } - test_data[] = - { - { path1, "" }, - { path2, TR_IF_WIN32("a", "a") }, - { path3, TR_IF_WIN32("\\", "/") }, - { path4, TR_IF_WIN32("\\a\\b\\c", "/a/b/c") }, - { path5, TR_IF_WIN32("C:\\a\\b\\c", "C:\\a/b\\c") }, - }; - - for (size_t i = 0; i < TR_N_ELEMENTS(test_data); ++i) - { - char* const output = tr_sys_path_native_separators(test_data[i].input); - - check_str(output, ==, test_data[i].output); - check_str(test_data[i].input, ==, test_data[i].output); - check_ptr(output, ==, test_data[i].input); - } - - return 0; -} - -static int test_file_open(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - tr_sys_file_t fd; - uint64_t n; - tr_sys_path_info info; - - path1 = tr_buildPath(test_dir, "a", NULL); - - /* Can't open non-existent file */ - check(!tr_sys_path_exists(path1, NULL)); - check(tr_sys_file_open(path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE); - check_ptr(err, !=, NULL); - check(!tr_sys_path_exists(path1, NULL)); - tr_error_clear(&err); - check(tr_sys_file_open(path1, TR_SYS_FILE_WRITE, 0600, &err) == TR_BAD_SYS_FILE); - check_ptr(err, !=, NULL); - check(!tr_sys_path_exists(path1, NULL)); - tr_error_clear(&err); - - /* Can't open directory */ - tr_sys_dir_create(path1, 0, 0777, NULL); -#ifdef _WIN32 - /* This works on *NIX */ - check(tr_sys_file_open(path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE); - check_ptr(err, !=, NULL); - tr_error_clear(&err); -#endif - check(tr_sys_file_open(path1, TR_SYS_FILE_WRITE, 0600, &err) == TR_BAD_SYS_FILE); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - tr_sys_path_remove(path1, NULL); - - /* Can create non-existent file */ - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0640, &err); - check_int(fd, !=, TR_BAD_SYS_FILE); - check_ptr(err, ==, NULL); - tr_sys_file_close(fd, NULL); - check(tr_sys_path_exists(path1, NULL)); - check(validate_permissions(path1, 0640)); - - /* Can open existing file */ - check(tr_sys_path_exists(path1, NULL)); - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0600, &err); - check_int(fd, !=, TR_BAD_SYS_FILE); - check_ptr(err, ==, NULL); - tr_sys_file_close(fd, NULL); - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE, 0600, &err); - check_int(fd, !=, TR_BAD_SYS_FILE); - check_ptr(err, ==, NULL); - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - libtest_create_file_with_string_contents(path1, "test"); - - /* Can't create new file if it already exists */ - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE_NEW, 0640, &err); - check_int(fd, ==, TR_BAD_SYS_FILE); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); - check_uint(info.size, ==, 4); - - /* Pointer is at the end of file */ - tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); - check_uint(info.size, ==, 4); - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_APPEND, 0600, &err); - check_int(fd, !=, TR_BAD_SYS_FILE); - check_ptr(err, ==, NULL); - tr_sys_file_write(fd, "s", 1, NULL, NULL); /* On *NIX, pointer is positioned on each write but not initially */ - tr_sys_file_seek(fd, 0, TR_SEEK_CUR, &n, NULL); - check_uint(n, ==, 5); - tr_sys_file_close(fd, NULL); - - /* File gets truncated */ - tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); - check_uint(info.size, ==, 5); - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_TRUNCATE, 0600, &err); - check_int(fd, !=, TR_BAD_SYS_FILE); - check_ptr(err, ==, NULL); - tr_sys_file_get_info(fd, &info, NULL); - check_uint(info.size, ==, 0); - tr_sys_file_close(fd, NULL); - tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, NULL); - check_uint(info.size, ==, 0); - - /* TODO: symlink and hardlink tests */ - - tr_sys_path_remove(path1, NULL); - - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_file_read_write_seek(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - tr_sys_file_t fd; - uint64_t n; - char buf[100]; - - path1 = tr_buildPath(test_dir, "a", NULL); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); - - check(tr_sys_file_seek(fd, 0, TR_SEEK_CUR, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 0); - - check(tr_sys_file_write(fd, "test", 4, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 4); - - check(tr_sys_file_seek(fd, 0, TR_SEEK_CUR, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 4); - - check(tr_sys_file_seek(fd, 0, TR_SEEK_SET, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 0); - - check(tr_sys_file_read(fd, buf, sizeof(buf), &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 4); - - check_mem(buf, ==, "test", 4); - - check(tr_sys_file_seek(fd, -3, TR_SEEK_CUR, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 1); - - check(tr_sys_file_write(fd, "E", 1, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 1); - - check(tr_sys_file_seek(fd, -2, TR_SEEK_CUR, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 0); - - check(tr_sys_file_read(fd, buf, sizeof(buf), &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 4); - - check_mem(buf, ==, "tEst", 4); - - check(tr_sys_file_seek(fd, 0, TR_SEEK_END, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 4); - - check(tr_sys_file_write(fd, " ok", 3, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 3); - - check(tr_sys_file_seek(fd, 0, TR_SEEK_SET, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 0); - - check(tr_sys_file_read(fd, buf, sizeof(buf), &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 7); - - check_mem(buf, ==, "tEst ok", 7); - - check(tr_sys_file_write_at(fd, "-", 1, 4, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 1); - - check(tr_sys_file_read_at(fd, buf, 5, 2, &n, &err)); - check_ptr(err, ==, NULL); - check_uint(n, ==, 5); - - check_mem(buf, ==, "st-ok", 5); - - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_file_truncate(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - tr_sys_file_t fd; - tr_sys_path_info info; - - path1 = tr_buildPath(test_dir, "a", NULL); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); - - check(tr_sys_file_truncate(fd, 10, &err)); - check_ptr(err, ==, NULL); - tr_sys_file_get_info(fd, &info, NULL); - check_uint(info.size, ==, 10); - - check(tr_sys_file_truncate(fd, 20, &err)); - check_ptr(err, ==, NULL); - tr_sys_file_get_info(fd, &info, NULL); - check_uint(info.size, ==, 20); - - check(tr_sys_file_truncate(fd, 0, &err)); - check_ptr(err, ==, NULL); - tr_sys_file_get_info(fd, &info, NULL); - check_uint(info.size, ==, 0); - - check(tr_sys_file_truncate(fd, 50, &err)); - check_ptr(err, ==, NULL); - - tr_sys_file_close(fd, NULL); - - tr_sys_path_get_info(path1, 0, &info, NULL); - check_uint(info.size, ==, 50); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); - - check(tr_sys_file_truncate(fd, 25, &err)); - check_ptr(err, ==, NULL); - - tr_sys_file_close(fd, NULL); - - tr_sys_path_get_info(path1, 0, &info, NULL); - check_uint(info.size, ==, 25); - - tr_sys_path_remove(path1, NULL); - - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_file_preallocate(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - tr_sys_file_t fd; - tr_sys_path_info info; - - path1 = tr_buildPath(test_dir, "a", NULL); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); - - if (tr_sys_file_preallocate(fd, 50, 0, &err)) - { - check_ptr(err, ==, NULL); - tr_sys_file_get_info(fd, &info, NULL); - check_uint(info.size, ==, 50); - } - else - { - check_ptr(err, !=, NULL); - fprintf(stderr, "WARNING: [%s] unable to preallocate file (full): %s (%d)\n", __FUNCTION__, err->message, err->code); - tr_error_clear(&err); - } - - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, NULL); - - if (tr_sys_file_preallocate(fd, 500 * 1024 * 1024, TR_SYS_FILE_PREALLOC_SPARSE, &err)) - { - check_ptr(err, ==, NULL); - tr_sys_file_get_info(fd, &info, NULL); - check_uint(info.size, ==, 500 * 1024 * 1024); - } - else - { - check_ptr(err, !=, NULL); - fprintf(stderr, "WARNING: [%s] unable to preallocate file (sparse): %s (%d)\n", __FUNCTION__, err->message, err->code); - tr_error_clear(&err); - } - - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_file_map(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - tr_sys_file_t fd; - char* view; - - path1 = tr_buildPath(test_dir, "a", NULL); - - libtest_create_file_with_string_contents(path1, "test"); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE, 0600, NULL); - - view = tr_sys_file_map_for_reading(fd, 0, 4, &err); - check_ptr(view, !=, NULL); - check_ptr(err, ==, NULL); - - check_mem(view, ==, "test", 4); - -#ifdef HAVE_UNIFIED_BUFFER_CACHE - - tr_sys_file_write_at(fd, "E", 1, 1, NULL, NULL); - - check_mem(view, ==, "tEst", 4); - -#endif - - check(tr_sys_file_unmap(view, 4, &err)); - check_ptr(err, ==, NULL); - - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_file_utilities(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - tr_sys_file_t fd; - char buffer[16]; - - path1 = tr_buildPath(test_dir, "a", NULL); - - libtest_create_file_with_string_contents(path1, "a\nbc\r\ndef\nghij\r\n\n\nklmno\r"); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0, NULL); - - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "a"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "bc"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "def"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "ghij"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, ""); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, ""); - check(tr_sys_file_read_line(fd, buffer, 4, &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "klmn"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "o"); - check(!tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "o"); /* on EOF, buffer stays unchanged */ - - tr_sys_file_close(fd, NULL); - - fd = tr_sys_file_open(path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE | TR_SYS_FILE_TRUNCATE, 0, NULL); - - check(tr_sys_file_write_line(fd, "p", &err)); - check_ptr(err, ==, NULL); - check(tr_sys_file_write_line(fd, "", &err)); - check_ptr(err, ==, NULL); - check(tr_sys_file_write_line(fd, "qr", &err)); - check_ptr(err, ==, NULL); - check(tr_sys_file_write_fmt(fd, "s%cu\r\n", &err, 't')); - check_ptr(err, ==, NULL); - check(tr_sys_file_write_line(fd, "", &err)); - check_ptr(err, ==, NULL); - check(tr_sys_file_write_line(fd, "", &err)); - check_ptr(err, ==, NULL); - check(tr_sys_file_write_fmt(fd, "v%sy%d", &err, "wx", 2)); - check_ptr(err, ==, NULL); - - tr_sys_file_seek(fd, 0, TR_SEEK_SET, NULL, NULL); - - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "p"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, ""); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "qr"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "stu"); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, ""); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, ""); - check(tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "vwxy2"); - check(!tr_sys_file_read_line(fd, buffer, TR_N_ELEMENTS(buffer), &err)); - check_ptr(err, ==, NULL); - check_str(buffer, ==, "vwxy2"); /* on EOF, buffer stays unchanged */ - - tr_sys_file_close(fd, NULL); - - tr_sys_path_remove(path1, NULL); - - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_dir_create(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - tr_error* err = NULL; - char* path1; - char* path2; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(path1, "b", NULL); - - /* Can create directory which has parent */ - check(tr_sys_dir_create(path1, 0, 0700, &err)); - check_ptr(err, ==, NULL); - check(tr_sys_path_exists(path1, NULL)); - check(validate_permissions(path1, 0700)); - - tr_sys_path_remove(path1, NULL); - libtest_create_file_with_string_contents(path1, "test"); - - /* Can't create directory where file already exists */ - check(!tr_sys_dir_create(path1, 0, 0700, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - check(!tr_sys_dir_create(path1, TR_SYS_DIR_CREATE_PARENTS, 0700, &err)); - check_ptr(err, !=, NULL); - tr_error_clear(&err); - - tr_sys_path_remove(path1, NULL); - - /* Can't create directory which has no parent */ - check(!tr_sys_dir_create(path2, 0, 0700, &err)); - check_ptr(err, !=, NULL); - check(!tr_sys_path_exists(path2, NULL)); - tr_error_clear(&err); - - /* Can create directory with parent directories */ - check(tr_sys_dir_create(path2, TR_SYS_DIR_CREATE_PARENTS, 0751, &err)); - check_ptr(err, ==, NULL); - check(tr_sys_path_exists(path1, NULL)); - check(tr_sys_path_exists(path2, NULL)); - check(validate_permissions(path1, 0751)); - check(validate_permissions(path2, 0751)); - - /* Can create existing directory (no-op) */ - check(tr_sys_dir_create(path1, 0, 0700, &err)); - check_ptr(err, ==, NULL); - check(tr_sys_dir_create(path1, TR_SYS_DIR_CREATE_PARENTS, 0700, &err)); - check_ptr(err, ==, NULL); - - tr_sys_path_remove(path2, NULL); - tr_sys_path_remove(path1, NULL); - - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -static int test_dir_read_impl(char const* path, bool* have1, bool* have2) -{ - tr_error* err = NULL; - tr_sys_dir_t dd; - char const* name; - - *have1 = *have2 = false; - - dd = tr_sys_dir_open(path, &err); - check_ptr(dd, !=, TR_BAD_SYS_DIR); - check_ptr(err, ==, NULL); - - while ((name = tr_sys_dir_read_name(dd, &err)) != NULL) - { - check_ptr(err, ==, NULL); - - if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) - { - continue; - } - - if (strcmp(name, "a") == 0) - { - *have1 = true; - } - else if (strcmp(name, "b") == 0) - { - *have2 = true; - } - else - { - check(false); - } - } - - check_ptr(err, ==, NULL); - - check(tr_sys_dir_close(dd, &err)); - check_ptr(err, ==, NULL); - - return 0; -} - -static int test_dir_read(void) -{ - char* const test_dir = create_test_dir(__FUNCTION__); - char* path1; - char* path2; - bool have1; - bool have2; - - path1 = tr_buildPath(test_dir, "a", NULL); - path2 = tr_buildPath(test_dir, "b", NULL); - - if (test_dir_read_impl(test_dir, &have1, &have2) != 0) - { - return 1; - } - - check(!have1); - check(!have2); - - libtest_create_file_with_string_contents(path1, "test"); - - if (test_dir_read_impl(test_dir, &have1, &have2) != 0) - { - return 1; - } - - check(have1); - check(!have2); - - libtest_create_file_with_string_contents(path2, "test"); - - if (test_dir_read_impl(test_dir, &have1, &have2) != 0) - { - return 1; - } - - check(have1); - check(have2); - - tr_sys_path_remove(path1, NULL); - - if (test_dir_read_impl(test_dir, &have1, &have2) != 0) - { - return 1; - } - - check(!have1); - check(have2); - - tr_free(path2); - tr_free(path1); - - tr_free(test_dir); - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - test_get_info, - test_path_exists, - test_path_is_relative, - test_path_is_same, - test_path_resolve, - test_path_basename_dirname, - test_path_rename, - test_path_remove, - test_path_native_separators, - test_file_open, - test_file_read_write_seek, - test_file_truncate, - test_file_preallocate, - test_file_map, - test_file_utilities, - test_dir_create, - test_dir_read - }; - - /* init the session */ - session = libttest_session_init(NULL); - - int ret = runTests(tests, NUM_TESTS(tests)); - - libttest_session_close(session); - - return ret; -} diff --git a/libtransmission/file.h b/libtransmission/file.h index aa40f41ce..9dc844595 100644 --- a/libtransmission/file.h +++ b/libtransmission/file.h @@ -15,10 +15,9 @@ #include #endif -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS struct tr_error; @@ -664,6 +663,4 @@ bool tr_sys_dir_close(tr_sys_dir_t handle, struct tr_error** error); /** @} */ /** @} */ -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/history-test.c b/libtransmission/history-test.c deleted file mode 100644 index 92ae1f298..000000000 --- a/libtransmission/history-test.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* memset() */ - -#include "transmission.h" -#include "history.h" - -#include "libtransmission-test.h" - -static int test1(void) -{ - tr_recentHistory h; - - memset(&h, 0, sizeof(tr_recentHistory)); - - tr_historyAdd(&h, 10000, 1); - check_int((int)tr_historyGet(&h, 12000, 1000), ==, 0); - check_int((int)tr_historyGet(&h, 12000, 3000), ==, 1); - check_int((int)tr_historyGet(&h, 12000, 5000), ==, 1); - tr_historyAdd(&h, 20000, 1); - check_int((int)tr_historyGet(&h, 22000, 1000), ==, 0); - check_int((int)tr_historyGet(&h, 22000, 3000), ==, 1); - check_int((int)tr_historyGet(&h, 22000, 15000), ==, 2); - check_int((int)tr_historyGet(&h, 22000, 20000), ==, 2); - - return 0; -} - -MAIN_SINGLE_TEST(test1) diff --git a/libtransmission/history.h b/libtransmission/history.h index e2b898dd0..8263f5cd5 100644 --- a/libtransmission/history.h +++ b/libtransmission/history.h @@ -12,6 +12,12 @@ #error only libtransmission should #include this header. #endif +#include /* time_t */ + +#include "tr-macros.h" + +TR_BEGIN_DECLS + /** * A generic short-term memory object that remembers how many times * something happened over the last N seconds. @@ -54,3 +60,5 @@ void tr_historyAdd(tr_recentHistory*, time_t when, unsigned int n); * @param seconds how many seconds to count back through. */ unsigned int tr_historyGet(tr_recentHistory const*, time_t when, unsigned int seconds); + +TR_END_DECLS diff --git a/libtransmission/json-test.c b/libtransmission/json-test.c deleted file mode 100644 index 9652e9cc3..000000000 --- a/libtransmission/json-test.c +++ /dev/null @@ -1,286 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* strlen() */ - -#include /* setlocale() */ - -#define __LIBTRANSMISSION_VARIANT_MODULE__ - -#include "transmission.h" -#include "utils.h" /* tr_free */ -#include "variant.h" -#include "variant-common.h" -#include "libtransmission-test.h" - -static int test_elements(void) -{ - char const* in; - tr_variant top; - char const* str; - bool f; - double d; - int64_t i; - int err = 0; - tr_quark key; - - in = - "{ \"string\": \"hello world\"," - " \"escaped\": \"bell \\b formfeed \\f linefeed \\n carriage return \\r tab \\t\"," - " \"int\": 5, " - " \"float\": 6.5, " - " \"true\": true, " - " \"false\": false, " - " \"null\": null }"; - - err = tr_variantFromJson(&top, in, strlen(in)); - check_int(err, ==, 0); - check(tr_variantIsDict(&top)); - str = NULL; - key = tr_quark_new("string", 6); - check(tr_variantDictFindStr(&top, key, &str, NULL)); - check_str(str, ==, "hello world"); - check(tr_variantDictFindStr(&top, tr_quark_new("escaped", 7), &str, NULL)); - check_str(str, ==, "bell \b formfeed \f linefeed \n carriage return \r tab \t"); - i = 0; - check(tr_variantDictFindInt(&top, tr_quark_new("int", 3), &i)); - check_int(i, ==, 5); - d = 0; - check(tr_variantDictFindReal(&top, tr_quark_new("float", 5), &d)); - check_int(((int)(d * 10)), ==, 65); - f = false; - check(tr_variantDictFindBool(&top, tr_quark_new("true", 4), &f)); - check_int(f, ==, true); - check(tr_variantDictFindBool(&top, tr_quark_new("false", 5), &f)); - check_int(f, ==, false); - check(tr_variantDictFindStr(&top, tr_quark_new("null", 4), &str, NULL)); - check_str(str, ==, ""); - - if (err == 0) - { - tr_variantFree(&top); - } - - return 0; -} - -static int test_utf8(void) -{ - char const* in = "{ \"key\": \"Letöltések\" }"; - tr_variant top; - char const* str; - char* json; - int err; - tr_quark const key = tr_quark_new("key", 3); - - err = tr_variantFromJson(&top, in, strlen(in)); - check_int(err, ==, 0); - check(tr_variantIsDict(&top)); - check(tr_variantDictFindStr(&top, key, &str, NULL)); - check_str(str, ==, "Letöltések"); - - if (err == 0) - { - tr_variantFree(&top); - } - - in = "{ \"key\": \"\\u005C\" }"; - err = tr_variantFromJson(&top, in, strlen(in)); - check_int(err, ==, 0); - check(tr_variantIsDict(&top)); - check(tr_variantDictFindStr(&top, key, &str, NULL)); - check_str(str, ==, "\\"); - - if (err == 0) - { - tr_variantFree(&top); - } - - /** - * 1. Feed it JSON-escaped nonascii to the JSON decoder. - * 2. Confirm that the result is UTF-8. - * 3. Feed the same UTF-8 back into the JSON encoder. - * 4. Confirm that the result is JSON-escaped. - * 5. Dogfood that result back into the parser. - * 6. Confirm that the result is UTF-8. - */ - in = "{ \"key\": \"Let\\u00f6lt\\u00e9sek\" }"; - err = tr_variantFromJson(&top, in, strlen(in)); - check_int(err, ==, 0); - check(tr_variantIsDict(&top)); - check(tr_variantDictFindStr(&top, key, &str, NULL)); - check_str(str, ==, "Letöltések"); - json = tr_variantToStr(&top, TR_VARIANT_FMT_JSON, NULL); - - if (err == 0) - { - tr_variantFree(&top); - } - - check_ptr(json, !=, NULL); - check_str(strstr(json, "\\u00f6"), !=, NULL); - check_str(strstr(json, "\\u00e9"), !=, NULL); - err = tr_variantFromJson(&top, json, strlen(json)); - check_int(err, ==, 0); - check(tr_variantIsDict(&top)); - check(tr_variantDictFindStr(&top, key, &str, NULL)); - check_str(str, ==, "Letöltések"); - - if (err == 0) - { - tr_variantFree(&top); - } - - tr_free(json); - - return 0; -} - -static int test1(void) -{ - char const* in = - "{\n" - " \"headers\": {\n" - " \"type\": \"request\",\n" - " \"tag\": 666\n" - " },\n" - " \"body\": {\n" - " \"name\": \"torrent-info\",\n" - " \"arguments\": {\n" - " \"ids\": [ 7, 10 ]\n" - " }\n" - " }\n" - "}\n"; - tr_variant top; - tr_variant* headers; - tr_variant* body; - tr_variant* args; - tr_variant* ids; - char const* str; - int64_t i; - int const err = tr_variantFromJson(&top, in, strlen(in)); - - check_int(err, ==, 0); - check(tr_variantIsDict(&top)); - check_ptr((headers = tr_variantDictFind(&top, tr_quark_new("headers", 7))), !=, NULL); - check(tr_variantIsDict(headers)); - check(tr_variantDictFindStr(headers, tr_quark_new("type", 4), &str, NULL)); - check_str(str, ==, "request"); - check(tr_variantDictFindInt(headers, TR_KEY_tag, &i)); - check_int(i, ==, 666); - check_ptr((body = tr_variantDictFind(&top, tr_quark_new("body", 4))), !=, NULL); - check(tr_variantDictFindStr(body, TR_KEY_name, &str, NULL)); - check_str(str, ==, "torrent-info"); - check_ptr((args = tr_variantDictFind(body, tr_quark_new("arguments", 9))), !=, NULL); - check(tr_variantIsDict(args)); - check_ptr((ids = tr_variantDictFind(args, TR_KEY_ids)), !=, NULL); - check(tr_variantIsList(ids)); - check_uint(tr_variantListSize(ids), ==, 2); - check(tr_variantGetInt(tr_variantListChild(ids, 0), &i)); - check_int(i, ==, 7); - check(tr_variantGetInt(tr_variantListChild(ids, 1), &i)); - check_int(i, ==, 10); - - tr_variantFree(&top); - return 0; -} - -static int test2(void) -{ - tr_variant top; - char const* in = " "; - int err; - - top.type = 0; - err = tr_variantFromJson(&top, in, strlen(in)); - - check_int(err, !=, 0); - check(!tr_variantIsDict(&top)); - - return 0; -} - -static int test3(void) -{ - char const* in = - "{ \"error\": 2," - " \"errorString\": \"torrent not registered with this tracker 6UHsVW'*C\"," - " \"eta\": 262792," - " \"id\": 25," - " \"leftUntilDone\": 2275655680 }"; - tr_variant top; - char const* str; - - int const err = tr_variantFromJson(&top, in, strlen(in)); - check_int(err, ==, 0); - check(tr_variantDictFindStr(&top, TR_KEY_errorString, &str, NULL)); - check_str(str, ==, "torrent not registered with this tracker 6UHsVW'*C"); - - tr_variantFree(&top); - return 0; -} - -static int test_unescape(void) -{ - char const* in = "{ \"string-1\": \"\\/usr\\/lib\" }"; - tr_variant top; - char const* str; - - int const err = tr_variantFromJson(&top, in, strlen(in)); - check_int(err, ==, 0); - check(tr_variantDictFindStr(&top, tr_quark_new("string-1", 8), &str, NULL)); - check_str(str, ==, "/usr/lib"); - - tr_variantFree(&top); - return 0; -} - -int main(void) -{ - char const* comma_locales[] = - { - "da_DK.UTF-8", - "fr_FR.UTF-8", - "ru_RU.UTF-8" - }; - - testFunc const tests[] = - { - test_elements, - test_utf8, - test1, - test2, - test3, - test_unescape - }; - - /* run the tests in a locale with a decimal point of '.' */ - setlocale(LC_NUMERIC, "C"); - - int ret = runTests(tests, NUM_TESTS(tests)); - - /* run the tests in a locale with a decimal point of ',' */ - bool is_locale_set = false; - - for (size_t i = 0; !is_locale_set && i < TR_N_ELEMENTS(comma_locales); ++i) - { - is_locale_set = setlocale(LC_NUMERIC, comma_locales[i]) != NULL; - } - - if (!is_locale_set) - { - fprintf(stderr, "WARNING: unable to run locale-specific json tests. add a locale like %s or %s\n", comma_locales[0], - comma_locales[1]); - } - else - { - ret += runTests(tests, NUM_TESTS(tests)); - } - - return ret; -} diff --git a/libtransmission/libtransmission-test.c b/libtransmission/libtransmission-test.c deleted file mode 100644 index ea8d81496..000000000 --- a/libtransmission/libtransmission-test.c +++ /dev/null @@ -1,589 +0,0 @@ -/* - * 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)) - { - return; - } - - tr_sys_dir_t odir = info.type == TR_SYS_PATH_IS_DIRECTORY ? tr_sys_dir_open(killme, NULL) : TR_BAD_SYS_DIR; - - if (odir != 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 -} diff --git a/libtransmission/libtransmission-test.h b/libtransmission/libtransmission-test.h deleted file mode 100644 index 6bf430b1a..000000000 --- a/libtransmission/libtransmission-test.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This file Copyright (C) 2010-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -/* Note VERBOSE needs to be (un)defined before including this file */ - -#pragma once - -#include -#include /* strlen() */ - -#include "transmission.h" -#include "utils.h" /* tr_strcmp0() */ - -extern int current_test; - -extern bool verbose; - -bool should_print(bool pass); - -bool libtest_check(char const* file, int line, bool pass, bool condition, char const* condition_str); -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); -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); -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); -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); -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); -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); - -/*** -**** -***/ - -#define check(condition) \ - do \ - { \ - ++current_test; \ - \ - bool const check_result = (condition); \ - \ - if (!libtest_check(__FILE__, __LINE__, check_result, check_result, #condition)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -#define check_bool(lhs, op, rhs) \ - do \ - { \ - ++current_test; \ - \ - bool const check_lhs = (lhs); \ - bool const check_rhs = (rhs); \ - \ - bool const check_result = check_lhs op check_rhs; \ - \ - if (!libtest_check_bool(__FILE__, __LINE__, check_result, check_lhs, check_rhs, #lhs, #op, #rhs)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -#define check_str(lhs, op, rhs) \ - do \ - { \ - ++current_test; \ - \ - char const* const check_lhs = (lhs); \ - char const* const check_rhs = (rhs); \ - \ - bool const check_result = tr_strcmp0(check_lhs, check_rhs) op 0; \ - \ - if (!libtest_check_str(__FILE__, __LINE__, check_result, check_lhs, check_rhs, #lhs, #op, #rhs)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -#define check_mem(lhs, op, rhs, size) \ - do \ - { \ - ++current_test; \ - \ - void const* const check_lhs = (lhs); \ - void const* const check_rhs = (rhs); \ - size_t const check_mem_size = (size); \ - \ - bool const check_result = tr_memcmp0(check_lhs, check_rhs, check_mem_size) op 0; \ - \ - if (!libtest_check_mem(__FILE__, __LINE__, check_result, check_lhs, check_rhs, check_mem_size, #lhs, #op, #rhs)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -#define check_int(lhs, op, rhs) \ - do \ - { \ - ++current_test; \ - \ - intmax_t const check_lhs = (lhs); \ - intmax_t const check_rhs = (rhs); \ - \ - bool const check_result = check_lhs op check_rhs; \ - \ - if (!libtest_check_int(__FILE__, __LINE__, check_result, check_lhs, check_rhs, #lhs, #op, #rhs)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -#define check_uint(lhs, op, rhs) \ - do \ - { \ - ++current_test; \ - \ - uintmax_t const check_lhs = (lhs); \ - uintmax_t const check_rhs = (rhs); \ - \ - bool const check_result = check_lhs op check_rhs; \ - \ - if (!libtest_check_uint(__FILE__, __LINE__, check_result, check_lhs, check_rhs, #lhs, #op, #rhs)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -#define check_ptr(lhs, op, rhs) \ - do \ - { \ - ++current_test; \ - \ - void const* const check_lhs = (lhs); \ - void const* const check_rhs = (rhs); \ - \ - bool const check_result = check_lhs op check_rhs; \ - \ - if (!libtest_check_ptr(__FILE__, __LINE__, check_result, check_lhs, check_rhs, #lhs, #op, #rhs)) \ - { \ - return current_test; \ - } \ - } \ - while (0) - -/*** -**** -***/ - -typedef int (* testFunc)(void); - -#define NUM_TESTS(tarray) ((int)(sizeof(tarray) / sizeof(tarray[0]))) - -int runTests(testFunc const* const tests, int numTests); - -#define MAIN_SINGLE_TEST(test) \ - int main(void) \ - { \ - testFunc const tests[] = { test }; \ - return runTests(tests, 1); \ - } - -tr_session* libttest_session_init(struct tr_variant* settings); -void libttest_session_close(tr_session* session); - -void libttest_zero_torrent_populate(tr_torrent* tor, bool complete); -tr_torrent* libttest_zero_torrent_init(tr_session* session); - -void libttest_blockingTorrentVerify(tr_torrent* tor); - -void libtest_create_file_with_contents(char const* path, void const* contents, size_t n); -void libtest_create_tmpfile_with_contents(char* tmpl, void const* payload, size_t n); -void libtest_create_file_with_string_contents(char const* path, char const* str); - -char* libtest_sandbox_create(void); -void libtest_sandbox_destroy(char const* sandbox); - -void libttest_sync(void); diff --git a/libtransmission/log.h b/libtransmission/log.h index 79aa68d0c..c08d2efb3 100644 --- a/libtransmission/log.h +++ b/libtransmission/log.h @@ -11,12 +11,10 @@ #include /* size_t */ #include "file.h" /* tr_sys_file_t */ +#include "tr-macros.h" #include "utils.h" /* TR_GNUC_PRINTF, TR_GNUC_NONNULL */ -#ifdef __cplusplus -extern "C" -{ -#endif +TR_BEGIN_DECLS #define TR_LOG_MAX_QUEUE_LENGTH 10000 @@ -77,8 +75,6 @@ void tr_logAddDeep(char const* file, int line, char const* name, char const* fmt /** @brief set the buffer with the current time formatted for deep logging. */ char* tr_logGetTimeStr(char* buf, size_t buflen) TR_GNUC_NONNULL(1); -#ifdef __cplusplus -} -#endif - /** @} */ + +TR_END_DECLS diff --git a/libtransmission/magnet-test.c b/libtransmission/magnet-test.c deleted file mode 100644 index d464ba159..000000000 --- a/libtransmission/magnet-test.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file Copyright (C) 2010-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include "transmission.h" -#include "magnet.h" -#include "utils.h" - -#include "libtransmission-test.h" - -static int test1(void) -{ - char const* uri; - tr_magnet_info* info; - uint8_t const dec[] = - { - 210, 53, 64, 16, 163, 202, 74, 222, 91, 116, - 39, 187, 9, 58, 98, 163, 137, 159, 243, 129 - }; - - uri = - "magnet:?xt=urn:btih:" - "d2354010a3ca4ade5b7427bb093a62a3899ff381" - "&dn=Display%20Name" - "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" - "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce" - "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"; - info = tr_magnetParse(uri); - check_ptr(info, !=, NULL); - check_int(info->trackerCount, ==, 2); - check_str(info->trackers[0], ==, "http://tracker.openbittorrent.com/announce"); - check_str(info->trackers[1], ==, "http://tracker.opentracker.org/announce"); - check_int(info->webseedCount, ==, 1); - check_str(info->webseeds[0], ==, "http://server.webseed.org/path/to/file"); - check_str(info->displayName, ==, "Display Name"); - check_mem(info->hash, ==, dec, 20); - - tr_magnetFree(info); - info = NULL; - - /* same thing but in base32 encoding */ - uri = - "magnet:?xt=urn:btih:" - "2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B" - "&dn=Display%20Name" - "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" - "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile" - "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"; - info = tr_magnetParse(uri); - check(info != NULL); - check_int(info->trackerCount, ==, 2); - check_str(info->trackers[0], ==, "http://tracker.openbittorrent.com/announce"); - check_str(info->trackers[1], ==, "http://tracker.opentracker.org/announce"); - check_int(info->webseedCount, ==, 1); - check_str(info->webseeds[0], ==, "http://server.webseed.org/path/to/file"); - check_str(info->displayName, ==, "Display Name"); - check_mem(info->hash, ==, dec, 20); - - tr_magnetFree(info); - info = NULL; - - return 0; -} - -MAIN_SINGLE_TEST(test1) diff --git a/libtransmission/magnet.h b/libtransmission/magnet.h index df5393304..38f3cd93f 100644 --- a/libtransmission/magnet.h +++ b/libtransmission/magnet.h @@ -12,9 +12,12 @@ #error only libtransmission should #include this header. #endif +#include "tr-macros.h" #include "transmission.h" #include "variant.h" +TR_BEGIN_DECLS + typedef struct tr_magnet_info { uint8_t hash[20]; @@ -36,3 +39,5 @@ struct tr_variant; void tr_magnetCreateMetainfo(tr_magnet_info const*, tr_variant*); void tr_magnetFree(tr_magnet_info* info); + +TR_END_DECLS diff --git a/libtransmission/makemeta-test.c b/libtransmission/makemeta-test.c deleted file mode 100644 index 0ef19a0a2..000000000 --- a/libtransmission/makemeta-test.c +++ /dev/null @@ -1,288 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include "libtransmission-test.h" - -#include "transmission.h" -#include "crypto-utils.h" -#include "file.h" -#include "makemeta.h" - -#include /* mktemp() */ -#include /* strlen() */ - -static int test_single_file_impl(tr_tracker_info const* trackers, size_t const trackerCount, void const* payload, - size_t const payloadSize, char const* comment, bool isPrivate) -{ - char* sandbox; - char* input_file; - char* torrent_file; - tr_metainfo_builder* builder; - tr_ctor* ctor; - tr_parse_result parse_result; - tr_info inf; - char* tmpstr; - - /* set up our local test sandbox */ - sandbox = libtest_sandbox_create(); - - /* create a single input file */ - input_file = tr_buildPath(sandbox, "test.XXXXXX", NULL); - libtest_create_tmpfile_with_contents(input_file, payload, payloadSize); - builder = tr_metaInfoBuilderCreate(input_file); - check_str(builder->top, ==, input_file); - check_int(builder->fileCount, ==, 1); - check_str(builder->files[0].filename, ==, input_file); - check_int(builder->files[0].size, ==, payloadSize); - check_int(builder->totalSize, ==, payloadSize); - check(!builder->isFolder); - check(!builder->abortFlag); - - /* have tr_makeMetaInfo() build the .torrent file */ - torrent_file = tr_strdup_printf("%s.torrent", input_file); - tr_makeMetaInfo(builder, torrent_file, trackers, trackerCount, comment, isPrivate); - check_bool(isPrivate, ==, builder->isPrivate); - check_str(builder->outputFile, ==, torrent_file); - check_str(builder->comment, ==, comment); - check_int(builder->trackerCount, ==, trackerCount); - - while (!builder->isDone) - { - tr_wait_msec(100); - } - - /* now let's check our work: parse the .torrent file */ - ctor = tr_ctorNew(NULL); - libttest_sync(); - tr_ctorSetMetainfoFromFile(ctor, torrent_file); - parse_result = tr_torrentParse(ctor, &inf); - check_int(parse_result, ==, TR_PARSE_OK); - - /* quick check of some of the parsed metainfo */ - check_int(inf.totalSize, ==, payloadSize); - tmpstr = tr_sys_path_basename(input_file, NULL); - check_str(inf.name, ==, tmpstr); - tr_free(tmpstr); - check_str(inf.comment, ==, comment); - check_int(inf.fileCount, ==, 1); - check_int(inf.isPrivate, ==, isPrivate); - check(!inf.isFolder); - check_int(inf.trackerCount, ==, trackerCount); - - /* cleanup */ - tr_free(torrent_file); - tr_free(input_file); - tr_ctorFree(ctor); - tr_metainfoFree(&inf); - tr_metaInfoBuilderFree(builder); - libtest_sandbox_destroy(sandbox); - tr_free(sandbox); - return 0; -} - -static int test_single_file(void) -{ - tr_tracker_info trackers[16]; - size_t trackerCount; - bool isPrivate; - char const* comment; - char const* payload; - size_t payloadSize; - - trackerCount = 0; - trackers[trackerCount].tier = trackerCount; - trackers[trackerCount].announce = (char*)"udp://tracker.openbittorrent.com:80"; - ++trackerCount; - trackers[trackerCount].tier = trackerCount; - trackers[trackerCount].announce = (char*)"udp://tracker.publicbt.com:80"; - ++trackerCount; - payload = "Hello, World!\n"; - payloadSize = strlen(payload); - comment = "This is the comment"; - isPrivate = false; - test_single_file_impl(trackers, trackerCount, payload, payloadSize, comment, isPrivate); - - return 0; -} - -static int test_single_directory_impl(tr_tracker_info const* trackers, size_t const trackerCount, void const** payloads, - size_t const* payloadSizes, size_t const payloadCount, char const* comment, - bool const isPrivate) -{ - char* sandbox; - char* torrent_file; - tr_metainfo_builder* builder; - tr_ctor* ctor; - tr_parse_result parse_result; - tr_info inf; - char* top; - char** files; - size_t totalSize; - char* tmpstr; - - /* set up our local test sandbox */ - sandbox = libtest_sandbox_create(); - - /* create the top temp directory */ - top = tr_buildPath(sandbox, "folder.XXXXXX", NULL); - tr_sys_dir_create_temp(top, NULL); - - /* build the payload files that go into the top temp directory */ - files = tr_new(char*, payloadCount); - totalSize = 0; - - for (size_t i = 0; i < payloadCount; i++) - { - char tmpl[16]; - tr_snprintf(tmpl, sizeof(tmpl), "file.%04zu%s", i, "XXXXXX"); - files[i] = tr_buildPath(top, tmpl, NULL); - libtest_create_tmpfile_with_contents(files[i], payloads[i], payloadSizes[i]); - totalSize += payloadSizes[i]; - } - - libttest_sync(); - - /* init the builder */ - builder = tr_metaInfoBuilderCreate(top); - check(!builder->abortFlag); - check_str(builder->top, ==, top); - check_int(builder->fileCount, ==, payloadCount); - check_int(builder->totalSize, ==, totalSize); - check(builder->isFolder); - - for (size_t i = 0; i < builder->fileCount; i++) - { - check_str(builder->files[i].filename, ==, files[i]); - check_int(builder->files[i].size, ==, payloadSizes[i]); - } - - /* call tr_makeMetaInfo() to build the .torrent file */ - torrent_file = tr_strdup_printf("%s.torrent", top); - tr_makeMetaInfo(builder, torrent_file, trackers, trackerCount, comment, isPrivate); - check_bool(isPrivate, ==, builder->isPrivate); - check_str(builder->outputFile, ==, torrent_file); - check_str(builder->comment, ==, comment); - check_int(builder->trackerCount, ==, trackerCount); - - while (!builder->isDone) - { - tr_wait_msec(100); - } - - /* now let's check our work: parse the .torrent file */ - ctor = tr_ctorNew(NULL); - libttest_sync(); - tr_ctorSetMetainfoFromFile(ctor, torrent_file); - parse_result = tr_torrentParse(ctor, &inf); - check_int(parse_result, ==, TR_PARSE_OK); - - /* quick check of some of the parsed metainfo */ - check_int(inf.totalSize, ==, totalSize); - tmpstr = tr_sys_path_basename(top, NULL); - check_str(inf.name, ==, tmpstr); - tr_free(tmpstr); - check_str(inf.comment, ==, comment); - check_int(inf.fileCount, ==, payloadCount); - check_int(inf.isPrivate, ==, isPrivate); - check_int(inf.isFolder, ==, builder->isFolder); - check_int(inf.trackerCount, ==, trackerCount); - - /* cleanup */ - tr_free(torrent_file); - tr_ctorFree(ctor); - tr_metainfoFree(&inf); - tr_metaInfoBuilderFree(builder); - - for (size_t i = 0; i < payloadCount; i++) - { - tr_free(files[i]); - } - - tr_free(files); - libtest_sandbox_destroy(sandbox); - tr_free(sandbox); - tr_free(top); - - return 0; -} - -static int test_single_directory_random_payload_impl(tr_tracker_info const* trackers, size_t const trackerCount, - size_t const maxFileCount, size_t const maxFileSize, char const* comment, - bool const isPrivate) -{ - void** payloads; - size_t* payloadSizes; - size_t payloadCount; - - /* build random payloads */ - payloadCount = 1 + tr_rand_int_weak(maxFileCount); - payloads = tr_new0(void*, payloadCount); - payloadSizes = tr_new0(size_t, payloadCount); - - for (size_t i = 0; i < payloadCount; i++) - { - size_t const n = 1 + tr_rand_int_weak(maxFileSize); - payloads[i] = tr_new(char, n); - tr_rand_buffer(payloads[i], n); - payloadSizes[i] = n; - } - - /* run the test */ - test_single_directory_impl(trackers, trackerCount, (void const**)payloads, payloadSizes, payloadCount, comment, isPrivate); - - /* cleanup */ - for (size_t i = 0; i < payloadCount; i++) - { - tr_free(payloads[i]); - } - - tr_free(payloads); - tr_free(payloadSizes); - - return 0; -} - -#define DEFAULT_MAX_FILE_COUNT 16 -#define DEFAULT_MAX_FILE_SIZE 1024 - -static int test_single_directory_random_payload(void) -{ - tr_tracker_info trackers[16]; - size_t trackerCount; - bool isPrivate; - char const* comment; - - trackerCount = 0; - trackers[trackerCount].tier = trackerCount; - trackers[trackerCount].announce = (char*)"udp://tracker.openbittorrent.com:80"; - ++trackerCount; - trackers[trackerCount].tier = trackerCount; - trackers[trackerCount].announce = (char*)"udp://tracker.publicbt.com:80"; - ++trackerCount; - comment = "This is the comment"; - isPrivate = false; - - for (size_t i = 0; i < 10; i++) - { - test_single_directory_random_payload_impl(trackers, trackerCount, DEFAULT_MAX_FILE_COUNT, DEFAULT_MAX_FILE_SIZE, - comment, isPrivate); - } - - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - test_single_file, - test_single_directory_random_payload - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/makemeta.c b/libtransmission/makemeta.c index 713f3e5cb..45606b974 100644 --- a/libtransmission/makemeta.c +++ b/libtransmission/makemeta.c @@ -43,12 +43,11 @@ static struct FileList* getFiles(char const* dir, char const* base, struct FileL return NULL; } - char* buf; + char* buf = tr_buildPath(dir, base, NULL); + tr_sys_path_native_separators(buf); + tr_sys_path_info info; tr_error* error = NULL; - - buf = tr_buildPath(dir, base, NULL); - if (!tr_sys_path_get_info(buf, 0, &info, &error)) { tr_logAddError(_("Torrent Creator is skipping file \"%s\": %s"), buf, error->message); diff --git a/libtransmission/makemeta.h b/libtransmission/makemeta.h index 4056769de..2cfe87db2 100644 --- a/libtransmission/makemeta.h +++ b/libtransmission/makemeta.h @@ -8,10 +8,10 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" +#include "transmission.h" + +TR_BEGIN_DECLS typedef struct tr_metainfo_builder_file { @@ -117,6 +117,4 @@ void tr_metaInfoBuilderFree(tr_metainfo_builder*); void tr_makeMetaInfo(tr_metainfo_builder* builder, char const* outputFile, tr_tracker_info const* trackers, int trackerCount, char const* comment, bool isPrivate); -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/metainfo.c b/libtransmission/metainfo.c index e42065ab3..8f00e4388 100644 --- a/libtransmission/metainfo.c +++ b/libtransmission/metainfo.c @@ -595,14 +595,14 @@ static char const* tr_metainfoParseImpl(tr_session const* session, tr_info* inf, } else { - size_t len; - char* bstr = tr_variantToStr(infoDict, TR_VARIANT_FMT_BENC, &len); - tr_sha1(inf->hash, bstr, (int)len, NULL); + size_t blen; + char* bstr = tr_variantToStr(infoDict, TR_VARIANT_FMT_BENC, &blen); + tr_sha1(inf->hash, bstr, (int)blen, NULL); tr_sha1_to_hex(inf->hashString, inf->hash); if (infoDictLength != NULL) { - *infoDictLength = len; + *infoDictLength = blen; } tr_free(bstr); @@ -706,9 +706,9 @@ static char const* tr_metainfoParseImpl(tr_session const* session, tr_info* inf, inf->pieceCount = len / SHA_DIGEST_LENGTH; inf->pieces = tr_new0(tr_piece, inf->pieceCount); - for (tr_piece_index_t i = 0; i < inf->pieceCount; i++) + for (tr_piece_index_t pi = 0; pi < inf->pieceCount; ++pi) { - memcpy(inf->pieces[i].hash, &raw[i * SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH); + memcpy(inf->pieces[pi].hash, &raw[pi * SHA_DIGEST_LENGTH], SHA_DIGEST_LENGTH); } } diff --git a/libtransmission/metainfo.h b/libtransmission/metainfo.h index edb307cc5..118fa4c02 100644 --- a/libtransmission/metainfo.h +++ b/libtransmission/metainfo.h @@ -14,6 +14,9 @@ #include "transmission.h" #include "variant.h" +#include "tr-macros.h" + +TR_BEGIN_DECLS enum tr_metainfo_basename_format { @@ -33,3 +36,5 @@ void tr_metainfoMigrateFile(tr_session const* session, tr_info const* info, enum /** @brief Private function that's exposed here only for unit tests */ char* tr_metainfo_sanitize_path_component(char const* str, size_t len, bool* is_adjusted); + +TR_END_DECLS diff --git a/libtransmission/move-test.c b/libtransmission/move-test.c deleted file mode 100644 index 87edb7d72..000000000 --- a/libtransmission/move-test.c +++ /dev/null @@ -1,240 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* strcmp() */ -#include - -#include - -#include "transmission.h" -#include "cache.h" -#include "file.h" -#include "resume.h" -#include "trevent.h" -#include "torrent.h" /* tr_isTorrent() */ -#include "variant.h" - -#include "libtransmission-test.h" - -/*** -**** -***/ - -static void zeroes_completeness_func(tr_torrent* torrent UNUSED, tr_completeness completeness, bool wasRunning UNUSED, - void* user_data) -{ - *(tr_completeness*)user_data = completeness; -} - -#define check_file_location(tor, i, expected_path) \ - do \ - { \ - char* path = tr_torrentFindFile(tor, i); \ - char* expected = expected_path; \ - check_str(path, ==, expected); \ - tr_free(expected); \ - tr_free(path); \ - } \ - while (0) - -struct test_incomplete_dir_data -{ - tr_session* session; - tr_torrent* tor; - tr_block_index_t block; - tr_piece_index_t pieceIndex; - uint32_t offset; - struct evbuffer* buf; - bool done; -}; - -static void test_incomplete_dir_threadfunc(void* vdata) -{ - struct test_incomplete_dir_data* data = vdata; - tr_cacheWriteBlock(data->session->cache, data->tor, 0, data->offset, data->tor->blockSize, data->buf); - tr_torrentGotBlock(data->tor, data->block); - data->done = true; -} - -static int test_incomplete_dir_impl(char const* incomplete_dir, char const* download_dir) -{ - tr_session* session; - tr_torrent* tor; - tr_completeness completeness; - tr_completeness const completeness_unset = -1; - time_t const deadline = time(NULL) + 300; - tr_variant settings; - - /* init the session */ - tr_variantInitDict(&settings, 3); - tr_variantDictAddStr(&settings, TR_KEY_download_dir, download_dir); - tr_variantDictAddStr(&settings, TR_KEY_incomplete_dir, incomplete_dir); - tr_variantDictAddBool(&settings, TR_KEY_incomplete_dir_enabled, true); - session = libttest_session_init(&settings); - tr_variantFree(&settings); - download_dir = tr_sessionGetDownloadDir(session); - incomplete_dir = tr_sessionGetIncompleteDir(session); - - /* init an incomplete torrent. - the test zero_torrent will be missing its first piece */ - tor = libttest_zero_torrent_init(session); - libttest_zero_torrent_populate(tor, false); - check_uint(tr_torrentStat(tor)->leftUntilDone, ==, tor->info.pieceSize); - check_file_location(tor, 0, tr_strdup_printf("%s/%s.part", incomplete_dir, tor->info.files[0].name)); - check_file_location(tor, 1, tr_buildPath(incomplete_dir, tor->info.files[1].name, NULL)); - check_uint(tr_torrentStat(tor)->leftUntilDone, ==, tor->info.pieceSize); - - completeness = completeness_unset; - tr_torrentSetCompletenessCallback(tor, zeroes_completeness_func, &completeness); - - /* now finish writing it */ - { - tr_block_index_t first; - tr_block_index_t last; - char* zero_block = tr_new0(char, tor->blockSize); - struct test_incomplete_dir_data data; - - data.session = session; - data.tor = tor; - data.pieceIndex = 0; - data.buf = evbuffer_new(); - - tr_torGetPieceBlockRange(tor, data.pieceIndex, &first, &last); - - for (tr_block_index_t block_index = first; block_index <= last; ++block_index) - { - evbuffer_add(data.buf, zero_block, tor->blockSize); - data.block = block_index; - data.done = false; - data.offset = data.block * tor->blockSize; - tr_runInEventThread(session, test_incomplete_dir_threadfunc, &data); - - do - { - tr_wait_msec(50); - } - while (!data.done); - } - - evbuffer_free(data.buf); - tr_free(zero_block); - } - - libttest_blockingTorrentVerify(tor); - check_uint(tr_torrentStat(tor)->leftUntilDone, ==, 0); - - while (completeness == completeness_unset && time(NULL) <= deadline) - { - tr_wait_msec(50); - } - - check_int(completeness, ==, TR_SEED); - - for (tr_file_index_t file_index = 0; file_index < tor->info.fileCount; ++file_index) - { - check_file_location(tor, file_index, tr_buildPath(download_dir, tor->info.files[file_index].name, NULL)); - } - - /* cleanup */ - tr_torrentRemove(tor, true, tr_sys_path_remove); - libttest_session_close(session); - return 0; -} - -static int test_incomplete_dir(void) -{ - int rv; - - /* test what happens when incompleteDir is a subdir of downloadDir*/ - if ((rv = test_incomplete_dir_impl("Downloads/Incomplete", "Downloads")) != 0) - { - return rv; - } - - /* test what happens when downloadDir is a subdir of incompleteDir */ - if ((rv = test_incomplete_dir_impl("Downloads", "Downloads/Complete")) != 0) - { - return rv; - } - - /* test what happens when downloadDir and incompleteDir are siblings */ - if ((rv = test_incomplete_dir_impl("Incomplete", "Downloads")) != 0) - { - return rv; - } - - return 0; -} - -/*** -**** -***/ - -static int test_set_location(void) -{ - int state; - char* target_dir; - tr_torrent* tor; - tr_session* session; - time_t const deadline = time(NULL) + 300; - - /* init the session */ - session = libttest_session_init(NULL); - target_dir = tr_buildPath(tr_sessionGetConfigDir(session), "target", NULL); - tr_sys_dir_create(target_dir, TR_SYS_DIR_CREATE_PARENTS, 0777, NULL); - - /* init a torrent. */ - tor = libttest_zero_torrent_init(session); - libttest_zero_torrent_populate(tor, true); - libttest_blockingTorrentVerify(tor); - check_uint(tr_torrentStat(tor)->leftUntilDone, ==, 0); - - /* now move it */ - state = -1; - tr_torrentSetLocation(tor, target_dir, true, NULL, &state); - - while (state == TR_LOC_MOVING && time(NULL) <= deadline) - { - tr_wait_msec(50); - } - - check_int(state, ==, TR_LOC_DONE); - - /* confirm the torrent is still complete after being moved */ - libttest_blockingTorrentVerify(tor); - check_uint(tr_torrentStat(tor)->leftUntilDone, ==, 0); - - /* confirm the filest really got moved */ - libttest_sync(); - - for (tr_file_index_t file_index = 0; file_index < tor->info.fileCount; ++file_index) - { - check_file_location(tor, file_index, tr_buildPath(target_dir, tor->info.files[file_index].name, NULL)); - } - - /* cleanup */ - tr_free(target_dir); - tr_torrentRemove(tor, true, tr_sys_path_remove); - libttest_session_close(session); - return 0; -} - -/*** -**** -***/ - -int main(void) -{ - testFunc const tests[] = - { - test_incomplete_dir, - test_set_location - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/natpmp.c b/libtransmission/natpmp.c index d838f0c75..d466bef55 100644 --- a/libtransmission/natpmp.c +++ b/libtransmission/natpmp.c @@ -168,11 +168,11 @@ int tr_natpmpPulse(struct tr_natpmp* nat, tr_port private_port, bool is_enabled, if (val >= 0) { - int const private_port = resp.pnu.newportmapping.privateport; + int const unmapped_port = resp.pnu.newportmapping.privateport; - tr_logAddNamedInfo(getKey(), _("no longer forwarding port %d"), private_port); + tr_logAddNamedInfo(getKey(), _("no longer forwarding port %d"), unmapped_port); - if (nat->private_port == private_port) + if (nat->private_port == unmapped_port) { nat->private_port = 0; nat->public_port = 0; diff --git a/libtransmission/net.c b/libtransmission/net.c index 9efb3f900..a67b68c02 100644 --- a/libtransmission/net.c +++ b/libtransmission/net.c @@ -34,6 +34,7 @@ #include +#include #include #include "transmission.h" @@ -766,3 +767,17 @@ bool tr_address_is_valid_for_peers(tr_address const* addr, tr_port port) return port != 0 && tr_address_is_valid(addr) && !isIPv6LinkLocalAddress(addr) && !isIPv4MappedAddress(addr) && !isMartianAddr(addr); } + +struct tr_peer_socket tr_peer_socket_tcp_create(tr_socket_t const handle) +{ + TR_ASSERT(handle != TR_BAD_SOCKET); + struct tr_peer_socket const ret = { .type = TR_PEER_SOCKET_TYPE_TCP, .handle.tcp = handle }; + return ret; +} + +struct tr_peer_socket tr_peer_socket_utp_create(struct UTPSocket* const handle) +{ + TR_ASSERT(handle != NULL); + struct tr_peer_socket const ret = { .type = TR_PEER_SOCKET_TYPE_UTP, .handle.utp = handle }; + return ret; +} diff --git a/libtransmission/net.h b/libtransmission/net.h index 1e4361061..7b54ec314 100644 --- a/libtransmission/net.h +++ b/libtransmission/net.h @@ -35,6 +35,10 @@ #include #endif +#include "tr-macros.h" + +TR_BEGIN_DECLS + #ifdef _WIN32 typedef SOCKET tr_socket_t; #define TR_BAD_SOCKET INVALID_SOCKET @@ -129,10 +133,6 @@ enum struct tr_session; -struct tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_address const* addr, tr_port port, bool clientIsSeed); - -struct tr_peer_socket tr_netOpenPeerUTPSocket(tr_session* session, tr_address const* addr, tr_port port, bool clientIsSeed); - tr_socket_t tr_netBindTCP(tr_address const* addr, tr_port port, bool suppressMsgs); tr_socket_t tr_netAccept(tr_session* session, tr_socket_t bound, tr_address* setme_addr, tr_port* setme_port); @@ -154,3 +154,5 @@ bool tr_net_hasIPv6(tr_port); char* tr_net_strerror(char* buf, size_t buflen, int err); unsigned char const* tr_globalIPv6(void); + +TR_END_DECLS diff --git a/libtransmission/peer-io.c b/libtransmission/peer-io.c index cbdc85a2f..f29f1a812 100644 --- a/libtransmission/peer-io.c +++ b/libtransmission/peer-io.c @@ -13,6 +13,7 @@ #include #include +#include #include #include "transmission.h" diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index c8cca3471..9a560e275 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -113,15 +113,15 @@ tr_peerIo* tr_peerIoNewOutgoing(tr_session* session, struct tr_bandwidth* parent uint8_t const* torrentHash, bool isSeed, bool utp); tr_peerIo* tr_peerIoNewIncoming(tr_session* session, struct tr_bandwidth* parent, struct tr_address const* addr, tr_port port, - struct tr_peer_socket socket); + struct tr_peer_socket const socket); void tr_peerIoRefImpl(char const* file, int line, tr_peerIo* io); -#define tr_peerIoRef(io) tr_peerIoRefImpl(__FILE__, __LINE__, (io)); +#define tr_peerIoRef(io) tr_peerIoRefImpl(__FILE__, __LINE__, (io)) void tr_peerIoUnrefImpl(char const* file, int line, tr_peerIo* io); -#define tr_peerIoUnref(io) tr_peerIoUnrefImpl(__FILE__, __LINE__, (io)); +#define tr_peerIoUnref(io) tr_peerIoUnrefImpl(__FILE__, __LINE__, (io)) #define PEER_IO_MAGIC_NUMBER 206745 diff --git a/libtransmission/peer-mgr.c b/libtransmission/peer-mgr.c index d1239cc16..890d862b9 100644 --- a/libtransmission/peer-mgr.c +++ b/libtransmission/peer-mgr.c @@ -13,6 +13,7 @@ #include +#include #include #include "transmission.h" @@ -60,8 +61,6 @@ enum /* max number of peers to ask for per second overall. * this throttle is to avoid overloading the router */ MAX_CONNECTIONS_PER_SECOND = 12, - /* */ - MAX_CONNECTIONS_PER_PULSE = (int)(MAX_CONNECTIONS_PER_SECOND * (RECONNECT_PERIOD_MSEC / 1000.0)), /* number of bad pieces a peer is allowed to send before we ban them */ MAX_BAD_PIECES_PER_PEER = 5, /* amount of time to keep a list of request pieces lying around @@ -2098,7 +2097,6 @@ static bool myHandshakeDoneCB(tr_handshake* handshake, tr_peerIo* io, bool readA else { tr_quark client; - tr_peerIo* io; char buf[128]; if (peer_id != NULL) @@ -2110,9 +2108,9 @@ static bool myHandshakeDoneCB(tr_handshake* handshake, tr_peerIo* io, bool readA client = TR_KEY_NONE; } - io = tr_handshakeStealIO(handshake); /* this steals its refcount too, which is balanced by our unref in peerDelete() */ - tr_peerIoSetParent(io, &s->tor->bandwidth); - createBitTorrentPeer(s->tor, io, atom, client); + tr_peerIo* stolen = tr_handshakeStealIO(handshake); /* this steals its refcount too, which is balanced by our unref in peerDelete() */ + tr_peerIoSetParent(stolen, &s->tor->bandwidth); + createBitTorrentPeer(s->tor, stolen, atom, client); success = true; } @@ -2559,17 +2557,17 @@ void tr_peerUpdateProgress(tr_torrent* tor, tr_peer* peer) } /* clamp the progress range */ - if (peer->progress < 0.0) + if (peer->progress < 0.0f) { - peer->progress = 0.0; + peer->progress = 0.0f; } - if (peer->progress > 1.0) + if (peer->progress > 1.0f) { - peer->progress = 1.0; + peer->progress = 1.0f; } - if (peer->atom != NULL && peer->progress >= 1.0) + if (peer->atom != NULL && peer->progress >= 1.0f) { atomSetSeed(tor->swarm, peer->atom); } @@ -2699,9 +2697,9 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor) return 0; } - size_t const n = tr_ptrArraySize(&s->peers); + size_t const peer_count = tr_ptrArraySize(&s->peers); - if (n == 0) + if (peer_count == 0) { return 0; } @@ -2709,7 +2707,7 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor) { tr_peer const** peers = (tr_peer const**)tr_ptrArrayBase(&s->peers); - for (size_t i = 0; i < n; ++i) + for (size_t i = 0; i < peer_count; ++i) { if (peers[i]->atom != NULL && atomIsSeed(peers[i]->atom)) { @@ -3779,6 +3777,7 @@ static void reconnectPulse(evutil_socket_t foo UNUSED, short bar UNUSED, void* v } /* try to make new peer connections */ + int const MAX_CONNECTIONS_PER_PULSE = (int)(MAX_CONNECTIONS_PER_SECOND * (RECONNECT_PERIOD_MSEC / 1000.0)); makeNewPeerConnections(mgr, MAX_CONNECTIONS_PER_PULSE); } @@ -4002,7 +4001,7 @@ static void atomPulse(evutil_socket_t foo UNUSED, short bar UNUSED, void* vmgr) s->pool = TR_PTR_ARRAY_INIT; qsort(keep, keepCount, sizeof(struct peer_atom*), compareAtomPtrsByAddress); - for (int i = 0; i < keepCount; ++i) + for (i = 0; i < keepCount; ++i) { tr_ptrArrayAppend(&s->pool, keep[i]); } diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index 2dd414a97..258da2c9b 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -20,6 +20,7 @@ #include "net.h" /* tr_address */ #include "peer-common.h" +#include "peer-socket.h" #include "quark.h" /** @@ -87,7 +88,7 @@ bool tr_peerMgrDidPeerRequest(tr_torrent const* torrent, tr_peer const* peer, tr void tr_peerMgrRebuildRequests(tr_torrent* torrent); -void tr_peerMgrAddIncoming(tr_peerMgr* manager, tr_address* addr, tr_port port, struct tr_peer_socket socket); +void tr_peerMgrAddIncoming(tr_peerMgr* manager, tr_address* addr, tr_port port, struct tr_peer_socket const socket); tr_pex* tr_peerMgrCompactToPex(void const* compact, size_t compactLen, uint8_t const* added_f, size_t added_f_len, size_t* setme_pex_count); diff --git a/libtransmission/peer-msgs.c b/libtransmission/peer-msgs.c index 47994750d..3f9e44564 100644 --- a/libtransmission/peer-msgs.c +++ b/libtransmission/peer-msgs.c @@ -1117,15 +1117,15 @@ static void parseUtMetadata(tr_peerMsgs* msgs, uint32_t msglen, struct evbuffer* } else { - tr_variant tmp; + tr_variant v; struct evbuffer* payload; struct evbuffer* out = msgs->outMessages; /* build the rejection message */ - tr_variantInitDict(&tmp, 2); - tr_variantDictAddInt(&tmp, TR_KEY_msg_type, METADATA_MSG_TYPE_REJECT); - tr_variantDictAddInt(&tmp, TR_KEY_piece, piece); - payload = tr_variantToBuf(&tmp, TR_VARIANT_FMT_BENC); + tr_variantInitDict(&v, 2); + tr_variantDictAddInt(&v, TR_KEY_msg_type, METADATA_MSG_TYPE_REJECT); + tr_variantDictAddInt(&v, TR_KEY_piece, piece); + payload = tr_variantToBuf(&v, TR_VARIANT_FMT_BENC); /* write it out as a LTEP message to our outMessages buffer */ evbuffer_add_uint32(out, 2 * sizeof(uint8_t) + evbuffer_get_length(payload)); @@ -1137,7 +1137,7 @@ static void parseUtMetadata(tr_peerMsgs* msgs, uint32_t msglen, struct evbuffer* /* cleanup */ evbuffer_free(payload); - tr_variantFree(&tmp); + tr_variantFree(&v); } } diff --git a/libtransmission/peer-socket.h b/libtransmission/peer-socket.h index 97cf8158e..a665e6de5 100644 --- a/libtransmission/peer-socket.h +++ b/libtransmission/peer-socket.h @@ -14,6 +14,9 @@ #include "net.h" #include "tr-assert.h" +#include "tr-macros.h" + +TR_BEGIN_DECLS enum tr_peer_socket_type { @@ -36,14 +39,15 @@ struct tr_peer_socket #define TR_PEER_SOCKET_INIT ((struct tr_peer_socket){ .type = TR_PEER_SOCKET_TYPE_NONE }) -static inline struct tr_peer_socket tr_peer_socket_tcp_create(tr_socket_t const handle) -{ - TR_ASSERT(handle != TR_BAD_SOCKET); - return (struct tr_peer_socket){ .type = TR_PEER_SOCKET_TYPE_TCP, .handle.tcp = handle }; -} +struct tr_peer_socket tr_peer_socket_tcp_create(tr_socket_t const handle); -static inline struct tr_peer_socket tr_peer_socket_utp_create(struct UTPSocket* const handle) -{ - TR_ASSERT(handle != NULL); - return (struct tr_peer_socket){ .type = TR_PEER_SOCKET_TYPE_UTP, .handle.utp = handle }; -} +struct tr_peer_socket tr_peer_socket_utp_create(struct UTPSocket* const handle); + +struct tr_session; +struct tr_address; + +struct tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_address const* addr, tr_port port, bool clientIsSeed); + +struct tr_peer_socket tr_netOpenPeerUTPSocket(tr_session* session, tr_address const* addr, tr_port port, bool clientIsSeed); + +TR_END_DECLS diff --git a/libtransmission/quark-test.c b/libtransmission/quark-test.c deleted file mode 100644 index 1c9ddde9c..000000000 --- a/libtransmission/quark-test.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* strlen() */ - -#include "transmission.h" -#include "quark.h" -#include "libtransmission-test.h" - -static int test_static_quarks(void) -{ - for (int i = 0; i < TR_N_KEYS; i++) - { - tr_quark q; - size_t len; - char const* str; - - str = tr_quark_get_string((tr_quark)i, &len); - check_uint(len, ==, strlen(str)); - check(tr_quark_lookup(str, len, &q)); - check_int((int)q, ==, i); - } - - for (int i = 0; i + 1 < TR_N_KEYS; i++) - { - size_t len1; - size_t len2; - char const* str1; - char const* str2; - - str1 = tr_quark_get_string((tr_quark)i, &len1); - str2 = tr_quark_get_string((tr_quark)(i + 1), &len2); - - check_str(str1, <, str2); - } - - tr_quark const q = tr_quark_new(NULL, TR_BAD_SIZE); - check_int((int)q, ==, TR_KEY_NONE); - check_str(tr_quark_get_string(q, NULL), ==, ""); - - return 0; -} - -MAIN_SINGLE_TEST(test_static_quarks) diff --git a/libtransmission/quark.h b/libtransmission/quark.h index f00c32858..767e065cb 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -8,10 +8,9 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS /* Quarks — a 2-way association between a string and a unique integer identifier */ typedef size_t tr_quark; @@ -435,6 +434,4 @@ tr_quark tr_quark_new(void const* str, size_t len); **** ***/ -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/rename-test.c b/libtransmission/rename-test.c deleted file mode 100644 index 5b21a807d..000000000 --- a/libtransmission/rename-test.c +++ /dev/null @@ -1,587 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include -#include /* fopen() */ -#include /* strcmp() */ - -#include "transmission.h" -#include "crypto-utils.h" -#include "file.h" -#include "resume.h" -#include "torrent.h" /* tr_isTorrent() */ -#include "tr-assert.h" -#include "variant.h" - -#include "libtransmission-test.h" - -/*** -**** -***/ - -static tr_session* session = NULL; - -#define check_have_none(tor, totalSize) \ - do \ - { \ - tr_stat const* tst = tr_torrentStat(tor); \ - check_int(tst->activity, ==, TR_STATUS_STOPPED); \ - check_int(tst->error, ==, TR_STAT_OK); \ - check_uint(tst->sizeWhenDone, ==, totalSize); \ - check_uint(tst->leftUntilDone, ==, totalSize); \ - check_uint(tor->info.totalSize, ==, totalSize); \ - check_uint(tst->haveValid, ==, 0); \ - } \ - while (0) - -static bool testFileExistsAndConsistsOfThisString(tr_torrent const* tor, tr_file_index_t fileIndex, char const* str) -{ - char* path; - size_t const str_len = strlen(str); - bool success = false; - - path = tr_torrentFindFile(tor, fileIndex); - - if (path != NULL) - { - TR_ASSERT(tr_sys_path_exists(path, NULL)); - - size_t contents_len; - uint8_t* contents = tr_loadFile(path, &contents_len, NULL); - - success = contents != NULL && str_len == contents_len && memcmp(contents, str, contents_len) == 0; - - tr_free(contents); - tr_free(path); - } - - return success; -} - -static void onRenameDone(tr_torrent* tor UNUSED, char const* oldpath UNUSED, char const* newname UNUSED, int error, - void* user_data) -{ - *(int*)user_data = error; -} - -static int torrentRenameAndWait(tr_torrent* tor, char const* oldpath, char const* newname) -{ - int error = -1; - tr_torrentRenamePath(tor, oldpath, newname, onRenameDone, &error); - - do - { - tr_wait_msec(10); - } - while (error == -1); - - return error; -} - -static void torrentRemoveAndWait(tr_torrent* tor, int expected_torrent_count) -{ - tr_torrentRemove(tor, false, NULL); - - while (tr_sessionCountTorrents(session) != expected_torrent_count) - { - tr_wait_msec(10); - } -} - -/*** -**** -***/ - -static void create_single_file_torrent_contents(char const* top) -{ - char* path = tr_buildPath(top, "hello-world.txt", NULL); - libtest_create_file_with_string_contents(path, "hello, world!\n"); - tr_free(path); -} - -static tr_torrent* create_torrent_from_base64_metainfo(tr_ctor* ctor, char const* metainfo_base64) -{ - int err; - size_t metainfo_len; - char* metainfo; - tr_torrent* tor; - - /* create the torrent ctor */ - metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len); - TR_ASSERT(metainfo != NULL); - TR_ASSERT(metainfo_len > 0); - 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); - return tor; -} - -static int test_single_filename_torrent(void) -{ - uint64_t loaded; - tr_torrent* tor; - char* tmpstr; - size_t const totalSize = 14; - tr_ctor* ctor; - tr_stat const* st; - - /* this is a single-file torrent whose file is hello-world.txt, holding the string "hello, world!" */ - ctor = tr_ctorNew(session); - tor = create_torrent_from_base64_metainfo(ctor, - "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" - "ZWkxMzU4NTQ5MDk4ZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDY6bGVuZ3RoaTE0ZTQ6bmFtZTE1" - "OmhlbGxvLXdvcmxkLnR4dDEyOnBpZWNlIGxlbmd0aGkzMjc2OGU2OnBpZWNlczIwOukboJcrkFUY" - "f6LvqLXBVvSHqCk6Nzpwcml2YXRlaTBlZWU="); - check(tr_isTorrent(tor)); - - /* sanity check the info */ - check_int(tor->info.fileCount, ==, 1); - check_str(tor->info.files[0].name, ==, "hello-world.txt"); - check(!tor->info.files[0].is_renamed); - - /* sanity check the (empty) stats */ - libttest_blockingTorrentVerify(tor); - check_have_none(tor, totalSize); - - create_single_file_torrent_contents(tor->currentDir); - - /* sanity check the stats again, now that we've added the file */ - libttest_blockingTorrentVerify(tor); - st = tr_torrentStat(tor); - check_int(st->activity, ==, TR_STATUS_STOPPED); - check_int(st->error, ==, TR_STAT_OK); - check_uint(st->leftUntilDone, ==, 0); - check_uint(st->haveUnchecked, ==, 0); - check_uint(st->desiredAvailable, ==, 0); - check_uint(st->sizeWhenDone, ==, totalSize); - check_uint(st->haveValid, ==, totalSize); - - /** - *** okay! we've finally put together all the scaffolding to test - *** renaming a single-file torrent - **/ - - /* confirm that bad inputs get caught */ - - check_int(torrentRenameAndWait(tor, "hello-world.txt", NULL), ==, EINVAL); - check_int(torrentRenameAndWait(tor, "hello-world.txt", ""), ==, EINVAL); - check_int(torrentRenameAndWait(tor, "hello-world.txt", "."), ==, EINVAL); - check_int(torrentRenameAndWait(tor, "hello-world.txt", ".."), ==, EINVAL); - check_int(torrentRenameAndWait(tor, "hello-world.txt", "hello-world.txt"), ==, 0); - check_int(torrentRenameAndWait(tor, "hello-world.txt", "hello/world.txt"), ==, EINVAL); - - check(!tor->info.files[0].is_renamed); - check_str(tor->info.files[0].name, ==, "hello-world.txt"); - - /*** - **** Now try a rename that should succeed - ***/ - - tmpstr = tr_buildPath(tor->currentDir, "hello-world.txt", NULL); - check(tr_sys_path_exists(tmpstr, NULL)); - check_str(tr_torrentName(tor), ==, "hello-world.txt"); - check_int(torrentRenameAndWait(tor, tor->info.name, "foobar"), ==, 0); - check(!tr_sys_path_exists(tmpstr, NULL)); /* confirm the old filename can't be found */ - tr_free(tmpstr); - check(tor->info.files[0].is_renamed); /* confirm the file's 'renamed' flag is set */ - check_str(tr_torrentName(tor), ==, "foobar"); /* confirm the torrent's name is now 'foobar' */ - check_str(tor->info.files[0].name, ==, "foobar"); /* confirm the file's name is now 'foobar' in our struct */ - check_str(strstr(tor->info.torrent, "foobar"), ==, NULL); /* confirm the name in the .torrent file hasn't changed */ - tmpstr = tr_buildPath(tor->currentDir, "foobar", NULL); - check(tr_sys_path_exists(tmpstr, NULL)); /* confirm the file's name is now 'foobar' on the disk */ - tr_free(tmpstr); - check(testFileExistsAndConsistsOfThisString(tor, 0, "hello, world!\n")); /* confirm the contents are right */ - - /* (while it's renamed: confirm that the .resume file remembers the changes) */ - tr_torrentSaveResume(tor); - libttest_sync(); - loaded = tr_torrentLoadResume(tor, ~0, ctor, NULL); - check_str(tr_torrentName(tor), ==, "foobar"); - check_uint((loaded & TR_FR_NAME), !=, 0); - - /*** - **** ...and rename it back again - ***/ - - tmpstr = tr_buildPath(tor->currentDir, "foobar", NULL); - check(tr_sys_path_exists(tmpstr, NULL)); - check_int(torrentRenameAndWait(tor, "foobar", "hello-world.txt"), ==, 0); - check(!tr_sys_path_exists(tmpstr, NULL)); - check(tor->info.files[0].is_renamed); - check_str(tor->info.files[0].name, ==, "hello-world.txt"); - check_str(tr_torrentName(tor), ==, "hello-world.txt"); - tr_free(tmpstr); - check(testFileExistsAndConsistsOfThisString(tor, 0, "hello, world!\n")); - - /* cleanup */ - tr_ctorFree(ctor); - torrentRemoveAndWait(tor, 0); - return 0; -} - -/*** -**** -**** -**** -***/ - -static void create_multifile_torrent_contents(char const* top) -{ - char* path; - - path = tr_buildPath(top, "Felidae", "Felinae", "Acinonyx", "Cheetah", "Chester", NULL); - libtest_create_file_with_string_contents(path, "It ain't easy bein' cheesy.\n"); - tr_free(path); - - path = tr_buildPath(top, "Felidae", "Pantherinae", "Panthera", "Tiger", "Tony", NULL); - libtest_create_file_with_string_contents(path, "They’re Grrrrreat!\n"); - tr_free(path); - - path = tr_buildPath(top, "Felidae", "Felinae", "Felis", "catus", "Kyphi", NULL); - libtest_create_file_with_string_contents(path, "Inquisitive\n"); - tr_free(path); - - path = tr_buildPath(top, "Felidae", "Felinae", "Felis", "catus", "Saffron", NULL); - libtest_create_file_with_string_contents(path, "Tough\n"); - tr_free(path); - - libttest_sync(); -} - -static int test_multifile_torrent(void) -{ - uint64_t loaded; - tr_torrent* tor; - tr_ctor* ctor; - char* str; - char* tmp; - static size_t const totalSize = 67; - tr_stat const* st; - tr_file const* files; - char const* strings[4]; - char const* expected_files[4] = - { - "Felidae/Felinae/Acinonyx/Cheetah/Chester", - "Felidae/Felinae/Felis/catus/Kyphi", - "Felidae/Felinae/Felis/catus/Saffron", - "Felidae/Pantherinae/Panthera/Tiger/Tony" - }; - char const* expected_contents[4] = - { - "It ain't easy bein' cheesy.\n", - "Inquisitive\n", - "Tough\n", - "They’re Grrrrreat!\n" - }; - - ctor = tr_ctorNew(session); - tor = create_torrent_from_base64_metainfo(ctor, - "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" - "ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4" - "ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp" - "MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw" - "YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo" - "bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx" - "MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp" - "dmF0ZWkwZWVl"); - check(tr_isTorrent(tor)); - files = tor->info.files; - - /* sanity check the info */ - check_str(tor->info.name, ==, "Felidae"); - check_uint(tor->info.totalSize, ==, totalSize); - check_uint(tor->info.fileCount, ==, 4); - - for (tr_file_index_t i = 0; i < 4; ++i) - { - check_str(files[i].name, ==, expected_files[i]); - } - - /* sanity check the (empty) stats */ - libttest_blockingTorrentVerify(tor); - check_have_none(tor, totalSize); - - /* build the local data */ - create_multifile_torrent_contents(tor->currentDir); - - /* sanity check the (full) stats */ - libttest_blockingTorrentVerify(tor); - st = tr_torrentStat(tor); - check_int(st->activity, ==, TR_STATUS_STOPPED); - check_int(st->error, ==, TR_STAT_OK); - check_uint(st->leftUntilDone, ==, 0); - check_uint(st->haveUnchecked, ==, 0); - check_uint(st->desiredAvailable, ==, 0); - check_uint(st->sizeWhenDone, ==, totalSize); - check_uint(st->haveValid, ==, totalSize); - - /** - *** okay! let's test renaming. - **/ - - /* rename a leaf... */ - check_int(torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus/Kyphi", "placeholder"), ==, 0); - check_str(files[1].name, ==, "Felidae/Felinae/Felis/catus/placeholder"); - check(testFileExistsAndConsistsOfThisString(tor, 1, "Inquisitive\n")); - - /* ...and back again */ - check_int(torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus/placeholder", "Kyphi"), ==, 0); - check_str(files[1].name, ==, "Felidae/Felinae/Felis/catus/Kyphi"); - testFileExistsAndConsistsOfThisString(tor, 1, "Inquisitive\n"); - - /* rename a branch... */ - check_int(torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus", "placeholder"), ==, 0); - check_str(files[0].name, ==, expected_files[0]); - check_str(files[1].name, ==, "Felidae/Felinae/Felis/placeholder/Kyphi"); - check_str(files[2].name, ==, "Felidae/Felinae/Felis/placeholder/Saffron"); - check_str(files[3].name, ==, expected_files[3]); - check(testFileExistsAndConsistsOfThisString(tor, 1, expected_contents[1])); - check(testFileExistsAndConsistsOfThisString(tor, 2, expected_contents[2])); - check(!files[0].is_renamed); - check(files[1].is_renamed); - check(files[2].is_renamed); - check(!files[3].is_renamed); - - /* (while the branch is renamed: confirm that the .resume file remembers the changes) */ - tr_torrentSaveResume(tor); - /* this is a bit dodgy code-wise, but let's make sure the .resume file got the name */ - tr_free(files[1].name); - tor->info.files[1].name = tr_strdup("gabba gabba hey"); - loaded = tr_torrentLoadResume(tor, ~0, ctor, NULL); - check_uint((loaded & TR_FR_FILENAMES), !=, 0); - check_str(files[0].name, ==, expected_files[0]); - check_str(files[1].name, ==, "Felidae/Felinae/Felis/placeholder/Kyphi"); - check_str(files[2].name, ==, "Felidae/Felinae/Felis/placeholder/Saffron"); - check_str(files[3].name, ==, expected_files[3]); - - /* ...and back again */ - check_int(torrentRenameAndWait(tor, "Felidae/Felinae/Felis/placeholder", "catus"), ==, 0); - - for (tr_file_index_t i = 0; i < 4; ++i) - { - check_str(files[i].name, ==, expected_files[i]); - check(testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i])); - } - - check(!files[0].is_renamed); - check(files[1].is_renamed); - check(files[2].is_renamed); - check(!files[3].is_renamed); - - /*** - **** Test it an incomplete torrent... - ***/ - - /* remove the directory Felidae/Felinae/Felis/catus */ - str = tr_torrentFindFile(tor, 1); - check_str(str, !=, NULL); - tr_sys_path_remove(str, NULL); - tr_free(str); - str = tr_torrentFindFile(tor, 2); - check_str(str, !=, NULL); - tr_sys_path_remove(str, NULL); - tmp = tr_sys_path_dirname(str, NULL); - tr_sys_path_remove(tmp, NULL); - tr_free(tmp); - tr_free(str); - libttest_sync(); - libttest_blockingTorrentVerify(tor); - testFileExistsAndConsistsOfThisString(tor, 0, expected_contents[0]); - - for (tr_file_index_t i = 1; i <= 2; ++i) - { - str = tr_torrentFindFile(tor, i); - check_str(str, ==, NULL); - tr_free(str); - } - - testFileExistsAndConsistsOfThisString(tor, 3, expected_contents[3]); - - /* rename a branch... */ - check_int(torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus", "foo"), ==, 0); - check_str(files[0].name, ==, expected_files[0]); - check_str(files[1].name, ==, "Felidae/Felinae/Felis/foo/Kyphi"); - check_str(files[2].name, ==, "Felidae/Felinae/Felis/foo/Saffron"); - check_str(files[3].name, ==, expected_files[3]); - - /* ...and back again */ - check_int(torrentRenameAndWait(tor, "Felidae/Felinae/Felis/foo", "catus"), ==, 0); - - for (tr_file_index_t i = 0; i < 4; ++i) - { - check_str(files[i].name, ==, expected_files[i]); - } - - check_int(torrentRenameAndWait(tor, "Felidae", "gabba"), ==, 0); - strings[0] = "gabba/Felinae/Acinonyx/Cheetah/Chester"; - strings[1] = "gabba/Felinae/Felis/catus/Kyphi"; - strings[2] = "gabba/Felinae/Felis/catus/Saffron"; - strings[3] = "gabba/Pantherinae/Panthera/Tiger/Tony"; - - for (tr_file_index_t i = 0; i < 4; ++i) - { - check_str(files[i].name, ==, strings[i]); - testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]); - } - - /* rename the root, then a branch, and then a leaf... */ - check_int(torrentRenameAndWait(tor, "gabba", "Felidae"), ==, 0); - check_int(torrentRenameAndWait(tor, "Felidae/Pantherinae/Panthera/Tiger", "Snow Leopard"), ==, 0); - check_int(torrentRenameAndWait(tor, "Felidae/Pantherinae/Panthera/Snow Leopard/Tony", "10.6"), ==, 0); - strings[0] = "Felidae/Felinae/Acinonyx/Cheetah/Chester"; - strings[1] = "Felidae/Felinae/Felis/catus/Kyphi"; - strings[2] = "Felidae/Felinae/Felis/catus/Saffron"; - strings[3] = "Felidae/Pantherinae/Panthera/Snow Leopard/10.6"; - - for (tr_file_index_t i = 0; i < 4; ++i) - { - check_str(files[i].name, ==, strings[i]); - testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]); - } - - tr_ctorFree(ctor); - torrentRemoveAndWait(tor, 0); - - /** - *** Test renaming prefixes (shouldn't work) - **/ - - ctor = tr_ctorNew(session); - tor = create_torrent_from_base64_metainfo(ctor, - "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" - "ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4" - "ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp" - "MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw" - "YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo" - "bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx" - "MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp" - "dmF0ZWkwZWVl"); - check(tr_isTorrent(tor)); - files = tor->info.files; - - /* rename prefix of top */ - check_int(torrentRenameAndWait(tor, "Feli", "FelidaeX"), ==, EINVAL); - check_str(tor->info.name, ==, "Felidae"); - check(!files[0].is_renamed); - check(!files[1].is_renamed); - check(!files[2].is_renamed); - check(!files[3].is_renamed); - - /* rename false path */ - check_int(torrentRenameAndWait(tor, "Felidae/FelinaeX", "Genus Felinae"), ==, EINVAL); - check_str(tor->info.name, ==, "Felidae"); - check(!files[0].is_renamed); - check(!files[1].is_renamed); - check(!files[2].is_renamed); - check(!files[3].is_renamed); - - /*** - **** - ***/ - - /* cleanup */ - tr_ctorFree(ctor); - torrentRemoveAndWait(tor, 0); - return 0; -} - -/*** -**** -***/ - -static int test_partial_file(void) -{ - tr_torrent* tor; - tr_stat const* st; - tr_file_stat* fst; - uint32_t const pieceCount = 33; - uint32_t const pieceSize = 32768; - uint32_t const length[] = { 1048576, 4096, 512 }; - uint64_t const totalSize = length[0] + length[1] + length[2]; - char const* strings[3]; - - /*** - **** create our test torrent with an incomplete .part file - ***/ - - tor = libttest_zero_torrent_init(session); - check_uint(tor->info.totalSize, ==, totalSize); - check_uint(tor->info.pieceSize, ==, pieceSize); - check_uint(tor->info.pieceCount, ==, pieceCount); - check_str(tor->info.files[0].name, ==, "files-filled-with-zeroes/1048576"); - check_str(tor->info.files[1].name, ==, "files-filled-with-zeroes/4096"); - check_str(tor->info.files[2].name, ==, "files-filled-with-zeroes/512"); - - libttest_zero_torrent_populate(tor, false); - fst = tr_torrentFiles(tor, NULL); - check_uint(fst[0].bytesCompleted, ==, length[0] - pieceSize); - check_uint(fst[1].bytesCompleted, ==, length[1]); - check_uint(fst[2].bytesCompleted, ==, length[2]); - tr_torrentFilesFree(fst, tor->info.fileCount); - st = tr_torrentStat(tor); - check_uint(st->sizeWhenDone, ==, totalSize); - check_uint(st->leftUntilDone, ==, pieceSize); - - /*** - **** - ***/ - - check_int(torrentRenameAndWait(tor, "files-filled-with-zeroes", "foo"), ==, 0); - check_int(torrentRenameAndWait(tor, "foo/1048576", "bar"), ==, 0); - strings[0] = "foo/bar"; - strings[1] = "foo/4096"; - strings[2] = "foo/512"; - - for (tr_file_index_t i = 0; i < 3; ++i) - { - check_str(tor->info.files[i].name, ==, strings[i]); - } - - strings[0] = "foo/bar.part"; - - for (tr_file_index_t i = 0; i < 3; ++i) - { - char* expected = tr_buildPath(tor->currentDir, strings[i], NULL); - char* path = tr_torrentFindFile(tor, i); - check_str(path, ==, expected); - tr_free(path); - tr_free(expected); - } - - torrentRemoveAndWait(tor, 0); - return 0; -} - -/*** -**** -***/ - -int main(void) -{ - testFunc const tests[] = - { - test_single_filename_torrent, - test_multifile_torrent, - test_partial_file - }; - - session = libttest_session_init(NULL); - - int ret = runTests(tests, NUM_TESTS(tests)); - - libttest_session_close(session); - - return ret; -} diff --git a/libtransmission/resume.c b/libtransmission/resume.c index 5fd29ff7e..0e1b624ac 100644 --- a/libtransmission/resume.c +++ b/libtransmission/resume.c @@ -593,7 +593,6 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor) uint8_t const* raw; size_t rawlen; tr_variant* l; - tr_variant* b; struct tr_bitfield blocks = TR_BITFIELD_INIT; if (tr_variantDictFindList(prog, TR_KEY_time_checked, &l)) @@ -667,6 +666,7 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor) err = NULL; tr_bitfieldConstruct(&blocks, tor->blockCount); + tr_variant* b; if ((b = tr_variantDictFind(prog, TR_KEY_blocks)) != NULL) { size_t buflen; diff --git a/libtransmission/resume.h b/libtransmission/resume.h index 0ff034fe0..ca268f67e 100644 --- a/libtransmission/resume.h +++ b/libtransmission/resume.h @@ -12,6 +12,10 @@ #error only libtransmission should #include this header. #endif +#include "tr-macros.h" + +TR_BEGIN_DECLS + enum { TR_FR_DOWNLOADED = (1 << 0), @@ -49,3 +53,5 @@ void tr_torrentSaveResume(tr_torrent* tor); void tr_torrentRemoveResume(tr_torrent const* tor); int tr_torrentRenameResume(tr_torrent const* tor, char const* newname); + +TR_END_DECLS diff --git a/libtransmission/rpc-test.c b/libtransmission/rpc-test.c deleted file mode 100644 index 33b219b10..000000000 --- a/libtransmission/rpc-test.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include "transmission.h" -#include "rpcimpl.h" -#include "utils.h" -#include "variant.h" - -#include "libtransmission-test.h" - -static int test_list(void) -{ - size_t len; - int64_t i; - char const* str; - tr_variant top; - - tr_rpc_parse_list_str(&top, "12", TR_BAD_SIZE); - check(tr_variantIsInt(&top)); - check(tr_variantGetInt(&top, &i)); - check_int(i, ==, 12); - tr_variantFree(&top); - - tr_rpc_parse_list_str(&top, "12", 1); - check(tr_variantIsInt(&top)); - check(tr_variantGetInt(&top, &i)); - check_int(i, ==, 1); - tr_variantFree(&top); - - tr_rpc_parse_list_str(&top, "6,7", TR_BAD_SIZE); - check(tr_variantIsList(&top)); - check_uint(tr_variantListSize(&top), ==, 2); - check(tr_variantGetInt(tr_variantListChild(&top, 0), &i)); - check_int(i, ==, 6); - check(tr_variantGetInt(tr_variantListChild(&top, 1), &i)); - check_int(i, ==, 7); - tr_variantFree(&top); - - tr_rpc_parse_list_str(&top, "asdf", TR_BAD_SIZE); - check(tr_variantIsString(&top)); - check(tr_variantGetStr(&top, &str, &len)); - check_uint(len, ==, 4); - check_str(str, ==, "asdf"); - tr_variantFree(&top); - - tr_rpc_parse_list_str(&top, "1,3-5", TR_BAD_SIZE); - check(tr_variantIsList(&top)); - check_uint(tr_variantListSize(&top), ==, 4); - check(tr_variantGetInt(tr_variantListChild(&top, 0), &i)); - check_int(i, ==, 1); - check(tr_variantGetInt(tr_variantListChild(&top, 1), &i)); - check_int(i, ==, 3); - check(tr_variantGetInt(tr_variantListChild(&top, 2), &i)); - check_int(i, ==, 4); - check(tr_variantGetInt(tr_variantListChild(&top, 3), &i)); - check_int(i, ==, 5); - tr_variantFree(&top); - - return 0; -} - -/*** -**** -***/ - -static void rpc_response_func(tr_session* session UNUSED, tr_variant* response, void* setme) -{ - *(tr_variant*)setme = *response; - tr_variantInitBool(response, false); -} - -static int test_session_get_and_set(void) -{ - tr_session* session; - tr_variant request; - tr_variant response; - tr_variant* args; - tr_torrent* tor; - - session = libttest_session_init(NULL); - tor = libttest_zero_torrent_init(session); - check_ptr(tor, !=, NULL); - - tr_variantInitDict(&request, 1); - tr_variantDictAddStr(&request, TR_KEY_method, "session-get"); - tr_rpc_request_exec_json(session, &request, rpc_response_func, &response); - tr_variantFree(&request); - - check(tr_variantIsDict(&response)); - check(tr_variantDictFindDict(&response, TR_KEY_arguments, &args)); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_down), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_time_begin), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_time_day), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_time_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_time_end), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_alt_speed_up), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_blocklist_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_blocklist_size), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_blocklist_url), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_cache_size_mb), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_config_dir), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_dht_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_download_dir), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_download_dir_free_space), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_download_queue_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_download_queue_size), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_encryption), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_idle_seeding_limit), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_idle_seeding_limit_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_incomplete_dir), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_incomplete_dir_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_lpd_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_peer_limit_global), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_peer_limit_per_torrent), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_peer_port), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_peer_port_random_on_start), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_pex_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_port_forwarding_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_queue_stalled_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_queue_stalled_minutes), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_rename_partial_files), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_rpc_version), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_rpc_version_minimum), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_script_torrent_done_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_script_torrent_done_filename), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_seed_queue_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_seed_queue_size), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_seedRatioLimit), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_seedRatioLimited), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_speed_limit_down), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_speed_limit_down_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_speed_limit_up), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_speed_limit_up_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_start_added_torrents), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_trash_original_torrent_files), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_units), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_utp_enabled), !=, NULL); - check_ptr(tr_variantDictFind(args, TR_KEY_version), !=, NULL); - tr_variantFree(&response); - - /* cleanup */ - tr_torrentRemove(tor, false, NULL); - libttest_session_close(session); - return 0; -} - -/*** -**** -***/ - -int main(void) -{ - testFunc const tests[] = - { - test_list, - test_session_get_and_set - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/rpcimpl.c b/libtransmission/rpcimpl.c index 272a1c469..2778e2562 100644 --- a/libtransmission/rpcimpl.c +++ b/libtransmission/rpcimpl.c @@ -121,7 +121,6 @@ static tr_torrent** getTorrents(tr_session* session, tr_variant* args, int* setm for (int i = 0; i < n; ++i) { - char const* str; tr_torrent* tor; tr_variant* node = tr_variantListChild(ids, i); @@ -730,9 +729,9 @@ static void initField(tr_torrent* const tor, tr_info const* const inf, tr_stat c { size_t byte_count = 0; void* bytes = tr_torrentCreatePieceBitfield(tor, &byte_count); - char* str = tr_base64_encode(bytes, byte_count, NULL); - tr_variantInitStr(initme, str != NULL ? str : "", TR_BAD_SIZE); - tr_free(str); + char* enc = tr_base64_encode(bytes, byte_count, NULL); + tr_variantInitStr(initme, enc != NULL ? enc : "", TR_BAD_SIZE); + tr_free(enc); tr_free(bytes); } else diff --git a/libtransmission/rpcimpl.h b/libtransmission/rpcimpl.h index 9391b3aee..4368c8911 100644 --- a/libtransmission/rpcimpl.h +++ b/libtransmission/rpcimpl.h @@ -8,14 +8,12 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif - #include "transmission.h" +#include "tr-macros.h" #include "variant.h" +TR_BEGIN_DECLS + /*** **** RPC processing ***/ @@ -32,6 +30,4 @@ void tr_rpc_request_exec_uri(tr_session* session, void const* request_uri, size_ void tr_rpc_parse_list_str(tr_variant* setme, char const* list_str, size_t list_str_len); -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/session-id.h b/libtransmission/session-id.h index 633f3013e..7a75b5348 100644 --- a/libtransmission/session-id.h +++ b/libtransmission/session-id.h @@ -8,10 +8,9 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS typedef struct tr_session_id* tr_session_id_t; @@ -52,6 +51,4 @@ char const* tr_session_id_get_current(tr_session_id_t session_id); */ bool tr_session_id_is_local(char const* session_id); -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/session-test.c b/libtransmission/session-test.c deleted file mode 100644 index aef1a8244..000000000 --- a/libtransmission/session-test.c +++ /dev/null @@ -1,139 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 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 -#include "transmission.h" -#include "session.h" -#include "session-id.h" -#include "utils.h" -#include "version.h" - -#undef VERBOSE -#include "libtransmission-test.h" - -static int testPeerId(void) -{ - uint8_t peer_id[PEER_ID_LEN + 1]; - - for (int i = 0; i < 100000; ++i) - { - int val = 0; - - tr_peerIdInit(peer_id); - - check_uint(strlen((char*)peer_id), ==, PEER_ID_LEN); - check_mem(peer_id, ==, PEERID_PREFIX, 8); - - for (int j = 8; j < PEER_ID_LEN; ++j) - { - char tmp[2] = { (char)peer_id[j], '\0' }; - val += strtoul(tmp, NULL, 36); - } - - check_int(val % 36, ==, 0); - } - - return 0; -} - -static int test_session_id(void) -{ - tr_session_id_t session_id; - char const* session_id_str_1 = NULL; - char const* session_id_str_2 = NULL; - char const* session_id_str_3 = NULL; - - check(!tr_session_id_is_local(NULL)); - check(!tr_session_id_is_local("")); - check(!tr_session_id_is_local("test")); - - session_id = tr_session_id_new(); - check_ptr(session_id, !=, NULL); - - tr_timeUpdate(0); - - session_id_str_1 = tr_session_id_get_current(session_id); - check_str(session_id_str_1, !=, NULL); - check_uint(strlen(session_id_str_1), ==, 48); - session_id_str_1 = tr_strdup(session_id_str_1); - - check(tr_session_id_is_local(session_id_str_1)); - - tr_timeUpdate(60 * 60 - 1); - - check(tr_session_id_is_local(session_id_str_1)); - - session_id_str_2 = tr_session_id_get_current(session_id); - check_str(session_id_str_2, !=, NULL); - check_uint(strlen(session_id_str_2), ==, 48); - check_str(session_id_str_2, ==, session_id_str_1); - - tr_timeUpdate(60 * 60); - - check(tr_session_id_is_local(session_id_str_1)); - - session_id_str_2 = tr_session_id_get_current(session_id); - check_str(session_id_str_2, !=, NULL); - check_uint(strlen(session_id_str_2), ==, 48); - check_str(session_id_str_2, !=, session_id_str_1); - session_id_str_2 = tr_strdup(session_id_str_2); - - check(tr_session_id_is_local(session_id_str_2)); - check(tr_session_id_is_local(session_id_str_1)); - - tr_timeUpdate(60 * 60 * 2); - - check(tr_session_id_is_local(session_id_str_2)); - check(tr_session_id_is_local(session_id_str_1)); - - session_id_str_3 = tr_session_id_get_current(session_id); - check_str(session_id_str_3, !=, NULL); - check_uint(strlen(session_id_str_3), ==, 48); - check_str(session_id_str_3, !=, session_id_str_2); - check_str(session_id_str_3, !=, session_id_str_1); - session_id_str_3 = tr_strdup(session_id_str_3); - - check(tr_session_id_is_local(session_id_str_3)); - check(tr_session_id_is_local(session_id_str_2)); - check(!tr_session_id_is_local(session_id_str_1)); - - tr_timeUpdate(60 * 60 * 10); - - check(tr_session_id_is_local(session_id_str_3)); - check(tr_session_id_is_local(session_id_str_2)); - check(!tr_session_id_is_local(session_id_str_1)); - - check(!tr_session_id_is_local(NULL)); - check(!tr_session_id_is_local("")); - check(!tr_session_id_is_local("test")); - - tr_session_id_free(session_id); - - check(!tr_session_id_is_local(session_id_str_3)); - check(!tr_session_id_is_local(session_id_str_2)); - check(!tr_session_id_is_local(session_id_str_1)); - - tr_free((char*)session_id_str_3); - tr_free((char*)session_id_str_2); - tr_free((char*)session_id_str_1); - - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - testPeerId, - test_session_id - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/session.c b/libtransmission/session.c index 48a090498..66442ba50 100644 --- a/libtransmission/session.c +++ b/libtransmission/session.c @@ -21,6 +21,7 @@ #include /* evdns_base_free() */ #include +#include #include // #define TR_SHOW_DEPRECATED @@ -1489,7 +1490,7 @@ static void turtleUpdateTable(struct tr_turtle_info* t) end += MINUTES_PER_DAY; } - for (int i = begin; i < end; ++i) + for (time_t i = begin; i < end; ++i) { tr_bitfieldAdd(b, (i + day * MINUTES_PER_DAY) % MINUTES_PER_WEEK); } diff --git a/libtransmission/session.h b/libtransmission/session.h index 4ccf7fe46..31f526dbb 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -17,9 +17,12 @@ #include "bandwidth.h" #include "bitfield.h" #include "net.h" +#include "tr-macros.h" #include "utils.h" #include "variant.h" +TR_BEGIN_DECLS + typedef enum { TR_NET_OK, @@ -320,3 +323,5 @@ bool tr_sessionGetActiveSpeedLimit_Bps(tr_session const* session, tr_direction d void tr_sessionGetNextQueuedTorrents(tr_session* session, tr_direction dir, size_t numwanted, tr_ptrArray* setme); int tr_sessionCountQueueFreeSlots(tr_session* session, tr_direction); + +TR_END_DECLS diff --git a/libtransmission/subprocess-test.c b/libtransmission/subprocess-test.c deleted file mode 100644 index e3d08f195..000000000 --- a/libtransmission/subprocess-test.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * This file Copyright (C) 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 "transmission.h" -#include "error.h" -#include "file.h" -#include "subprocess.h" -#include "utils.h" - -#include "libtransmission-test.h" - -static char arg_dump_args[] = "--dump-args"; -static char arg_dump_env[] = "--dump-env"; -static char arg_dump_cwd[] = "--dump-cwd"; - -static char* self_path = NULL; - -static int test_spawn_async_missing_exe(void) -{ - char missing_exe_path[] = TR_IF_WIN32("C:\\", "/") "tr-missing-test-exe" TR_IF_WIN32(".exe", ""); - - char* const args[] = - { - missing_exe_path, - NULL - }; - - tr_error* error = NULL; - bool const ret = tr_spawn_async(args, NULL, NULL, &error); - check_bool(ret, ==, false); - check_ptr(error, !=, NULL); - check_int(error->code, !=, 0); - check_str(error->message, !=, NULL); - - tr_error_clear(&error); - - return 0; -} - -static int test_spawn_async_args(void) -{ - char* const test_dir = libtest_sandbox_create(); - char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL)); - bool const allow_batch_metachars = TR_IF_WIN32(false, true) || !tr_str_has_suffix(self_path, ".cmd"); - - char test_arg_1[] = "arg1 "; - char test_arg_2[] = " arg2"; - char test_arg_3[] = ""; - char test_arg_4[] = "\"arg3'^! $PATH %PATH% \\"; - - char* const args[] = - { - self_path, - result_path, - arg_dump_args, - test_arg_1, - test_arg_2, - test_arg_3, - allow_batch_metachars ? test_arg_4 : NULL, - NULL - }; - - tr_error* error = NULL; - bool const ret = tr_spawn_async(args, NULL, NULL, &error); - check_bool(ret, ==, true); - check_ptr(error, ==, NULL); - - while (!tr_sys_path_exists(result_path, NULL)) - { - tr_wait_msec(10); - } - - tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL); - check_int(fd, !=, TR_BAD_SYS_FILE); - - char buffer[1024]; - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_arg_1); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_arg_2); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_arg_3); - - if (allow_batch_metachars) - { - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_arg_4); - } - - check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - - tr_sys_file_close(fd, NULL); - - tr_free(result_path); - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_spawn_async_env(void) -{ - char* const test_dir = libtest_sandbox_create(); - char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL)); - - char test_env_key_1[] = "VAR1"; - char test_env_key_2[] = "_VAR_2_"; - char test_env_key_3[] = "vAr#"; - char test_env_key_4[] = "FOO"; - char test_env_key_5[] = "ZOO"; - char test_env_key_6[] = "TR_MISSING_TEST_ENV_KEY"; - - char test_env_value_1[] = "value1 "; - char test_env_value_2[] = " value2"; - char test_env_value_3[] = " \"value3'^! $PATH %PATH% "; - char test_env_value_4[] = "bar"; - char test_env_value_5[] = "jar"; - - char* const args[] = - { - self_path, - result_path, - arg_dump_env, - test_env_key_1, - test_env_key_2, - test_env_key_3, - test_env_key_4, - test_env_key_5, - test_env_key_6, - NULL - }; - - char* const env[] = - { - tr_strdup_printf("%s=%s", test_env_key_1, test_env_value_1), - tr_strdup_printf("%s=%s", test_env_key_2, test_env_value_2), - tr_strdup_printf("%s=%s", test_env_key_3, test_env_value_3), - tr_strdup_printf("%s=%s", test_env_key_5, test_env_value_5), - NULL - }; - - /* Inherited */ - char foo_env_value[] = "FOO=bar"; - putenv(foo_env_value); - - /* Overridden */ - char zoo_env_value[] = "ZOO=tar"; - putenv(zoo_env_value); - - tr_error* error = NULL; - bool const ret = tr_spawn_async(args, env, NULL, &error); - check_bool(ret, ==, true); - check_ptr(error, ==, NULL); - - while (!tr_sys_path_exists(result_path, NULL)) - { - tr_wait_msec(10); - } - - tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL); - check_int(fd, !=, TR_BAD_SYS_FILE); - - char buffer[1024]; - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_env_value_1); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_env_value_2); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_env_value_3); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_env_value_4); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, test_env_value_5); - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(buffer, ==, ""); - - check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - - tr_sys_file_close(fd, NULL); - - tr_free_ptrv((void* const*)env); - tr_free(result_path); - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_spawn_async_cwd_explicit(void) -{ - char* const test_dir = libtest_sandbox_create(); - char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL)); - - char* const args[] = - { - self_path, - result_path, - arg_dump_cwd, - NULL - }; - - tr_error* error = NULL; - bool const ret = tr_spawn_async(args, NULL, test_dir, &error); - check_bool(ret, ==, true); - check_ptr(error, ==, NULL); - - while (!tr_sys_path_exists(result_path, NULL)) - { - tr_wait_msec(10); - } - - tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL); - check_int(fd, !=, TR_BAD_SYS_FILE); - - char buffer[1024]; - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(tr_sys_path_native_separators(buffer), ==, tr_sys_path_native_separators(test_dir)); - - check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - - tr_sys_file_close(fd, NULL); - - tr_free(result_path); - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_spawn_async_cwd_inherit(void) -{ - char* const test_dir = libtest_sandbox_create(); - char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL)); - - char* const expected_cwd = tr_sys_dir_get_current(NULL); - - char* const args[] = - { - self_path, - result_path, - arg_dump_cwd, - NULL - }; - - tr_error* error = NULL; - bool const ret = tr_spawn_async(args, NULL, NULL, &error); - check_bool(ret, ==, true); - check_ptr(error, ==, NULL); - - while (!tr_sys_path_exists(result_path, NULL)) - { - tr_wait_msec(10); - } - - tr_sys_file_t fd = tr_sys_file_open(result_path, TR_SYS_FILE_READ, 0, NULL); - check_int(fd, !=, TR_BAD_SYS_FILE); - - char buffer[1024]; - - check(tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - check_str(tr_sys_path_native_separators(buffer), ==, tr_sys_path_native_separators(expected_cwd)); - - check(!tr_sys_file_read_line(fd, buffer, sizeof(buffer), NULL)); - - tr_sys_file_close(fd, NULL); - - tr_free(expected_cwd); - tr_free(result_path); - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_spawn_async_cwd_missing(void) -{ - char* const test_dir = libtest_sandbox_create(); - char* const result_path = tr_sys_path_native_separators(tr_buildPath(test_dir, "result.txt", NULL)); - - char* const args[] = - { - self_path, - result_path, - arg_dump_cwd, - NULL - }; - - tr_error* error = NULL; - bool const ret = tr_spawn_async(args, NULL, TR_IF_WIN32("C:\\", "/") "tr-missing-test-work-dir", &error); - check_bool(ret, ==, false); - check_ptr(error, !=, NULL); - check_int(error->code, !=, 0); - check_str(error->message, !=, NULL); - - tr_error_clear(&error); - - tr_free(result_path); - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -int main(int argc, char** argv) -{ - self_path = tr_sys_path_resolve(argv[0], NULL); - - if (argc >= 3) - { - char* const result_path = argv[1]; - char* const test_action = argv[2]; - - char* const tmp_result_path = tr_strdup_printf("%s.tmp", result_path); - - tr_sys_file_t const fd = tr_sys_file_open(tmp_result_path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | - TR_SYS_FILE_TRUNCATE, 0644, NULL); - - if (fd == TR_BAD_SYS_FILE) - { - tr_free(tmp_result_path); - return 1; - } - - if (strcmp(test_action, arg_dump_args) == 0) - { - for (int i = 3; i < argc; ++i) - { - tr_sys_file_write_line(fd, argv[i], NULL); - } - } - else if (strcmp(test_action, arg_dump_env) == 0) - { - for (int i = 3; i < argc; ++i) - { - char* const value = tr_env_get_string(argv[i], ""); - tr_sys_file_write_line(fd, value, NULL); - tr_free(value); - } - } - else if (strcmp(test_action, arg_dump_cwd) == 0) - { - char* const value = tr_sys_dir_get_current(NULL); - tr_sys_file_write_line(fd, value != NULL ? value : "", NULL); - tr_free(value); - } - else - { - tr_sys_file_close(fd, NULL); - tr_sys_path_remove(tmp_result_path, NULL); - - tr_free(tmp_result_path); - return 1; - } - - tr_sys_file_close(fd, NULL); - tr_sys_path_rename(tmp_result_path, result_path, NULL); - - tr_free(tmp_result_path); - return 0; - } - - testFunc const tests[] = - { - test_spawn_async_missing_exe, - test_spawn_async_args, - test_spawn_async_env, - test_spawn_async_cwd_explicit, - test_spawn_async_cwd_inherit, - test_spawn_async_cwd_missing - }; - - int ret = runTests(tests, NUM_TESTS(tests)); - -#ifdef _WIN32 - - strcpy(self_path + strlen(self_path) - 4, ".cmd"); - - int ret2 = runTests(tests, NUM_TESTS(tests)); - - if (ret == 0) - { - ret = ret2; - } - -#endif - - tr_free(self_path); - return ret; -} diff --git a/libtransmission/subprocess.h b/libtransmission/subprocess.h index 79ac7b4e4..684b32c67 100644 --- a/libtransmission/subprocess.h +++ b/libtransmission/subprocess.h @@ -8,4 +8,10 @@ #pragma once +#include "tr-macros.h" + +TR_BEGIN_DECLS + bool tr_spawn_async(char* const* cmd, char* const* env, char const* work_dir, struct tr_error** error); + +TR_END_DECLS diff --git a/libtransmission/torrent.c b/libtransmission/torrent.c index 7ecaf3f51..e89f840bd 100644 --- a/libtransmission/torrent.c +++ b/libtransmission/torrent.c @@ -1301,14 +1301,14 @@ tr_torrent_activity tr_torrentGetActivity(tr_torrent const* tor) return ret; } -static time_t torrentGetIdleSecs(tr_torrent const* tor) +static int torrentGetIdleSecs(tr_torrent const* tor) { int idle_secs; tr_torrent_activity const activity = tr_torrentGetActivity(tor); if ((activity == TR_STATUS_DOWNLOAD || activity == TR_STATUS_SEED) && tor->startDate != 0) { - idle_secs = difftime(tr_time(), MAX(tor->startDate, tor->activityDate)); + idle_secs = (int)difftime(tr_time(), MAX(tor->startDate, tor->activityDate)); } else { diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index e0c9aca3b..116a96097 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -16,9 +16,12 @@ #include "completion.h" /* tr_completion */ #include "session.h" /* tr_sessionLock(), tr_sessionUnlock() */ #include "tr-assert.h" +#include "tr-macros.h" #include "utils.h" /* TR_GNUC_PRINTF */ #include "ptrarray.h" +TR_BEGIN_DECLS + struct tr_torrent_tiers; struct tr_magnet_info; @@ -466,3 +469,5 @@ static inline tr_direction tr_torrentGetQueueDirection(tr_torrent const* tor) { return tr_torrentIsSeed(tor) ? TR_UP : TR_DOWN; } + +TR_END_DECLS diff --git a/libtransmission/tr-dht.c b/libtransmission/tr-dht.c index 039392e34..8f41aaffe 100644 --- a/libtransmission/tr-dht.c +++ b/libtransmission/tr-dht.c @@ -66,7 +66,7 @@ static struct event* dht_timer = NULL; static unsigned char myid[20]; -static tr_session* session = NULL; +static tr_session* session_ = NULL; static void timer_callback(evutil_socket_t s, short type, void* ignore); @@ -145,7 +145,7 @@ static void bootstrap_from_name(char const* name, tr_port port, int af) nap(15); - if (bootstrap_done(session, af)) + if (bootstrap_done(session_, af)) { break; } @@ -162,7 +162,7 @@ static void dht_bootstrap(void* closure) int num = cl->len / 6; int num6 = cl->len6 / 18; - if (session != cl->session) + if (session_ != cl->session) { return; } @@ -217,7 +217,7 @@ static void dht_bootstrap(void* closure) nap(15); } - if (bootstrap_done(session, 0)) + if (bootstrap_done(session_, 0)) { break; } @@ -265,7 +265,7 @@ static void dht_bootstrap(void* closure) *p = '\0'; - bootstrap_from_name(buf, port, bootstrap_af(session)); + bootstrap_from_name(buf, port, bootstrap_af(session_)); if (bootstrap_done(cl->session, 0)) { @@ -299,7 +299,7 @@ static void dht_bootstrap(void* closure) tr_logAddNamedInfo("DHT", "Attempting bootstrap from dht.transmissionbt.com"); } - bootstrap_from_name("dht.transmissionbt.com", 6881, bootstrap_af(session)); + bootstrap_from_name("dht.transmissionbt.com", 6881, bootstrap_af(session_)); } } @@ -330,7 +330,7 @@ int tr_dhtInit(tr_session* ss) size_t len6; struct bootstrap_closure* cl; - if (session != NULL) /* already initialized */ + if (session_ != NULL) /* already initialized */ { return -1; } @@ -397,17 +397,17 @@ int tr_dhtInit(tr_session* ss) goto fail; } - session = ss; + session_ = ss; cl = tr_new(struct bootstrap_closure, 1); - cl->session = session; + cl->session = session_; cl->nodes = nodes; cl->nodes6 = nodes6; cl->len = len; cl->len6 = len6; tr_threadNew(dht_bootstrap, cl); - dht_timer = evtimer_new(session->event_base, timer_callback, session); + dht_timer = evtimer_new(session_->event_base, timer_callback, session_); tr_timerAdd(dht_timer, 0, tr_rand_int_weak(1000000)); tr_logAddNamedDbg("DHT", "DHT initialized"); @@ -419,13 +419,13 @@ fail: tr_free(nodes); tr_logAddNamedDbg("DHT", "DHT initialization failed (errno = %d)", errno); - session = NULL; + session_ = NULL; return -1; } void tr_dhtUninit(tr_session* ss) { - if (session != ss) + if (session_ != ss) { return; } @@ -492,12 +492,12 @@ void tr_dhtUninit(tr_session* ss) dht_uninit(); tr_logAddNamedDbg("DHT", "Done uninitializing DHT"); - session = NULL; + session_ = NULL; } bool tr_dhtEnabled(tr_session const* ss) { - return ss != NULL && ss == session; + return ss != NULL && ss == session_; } struct getstatus_closure @@ -644,8 +644,8 @@ static void callback(void* ignore UNUSED, int event, unsigned char const* info_h if (event == DHT_EVENT_VALUES || event == DHT_EVENT_VALUES6) { tr_torrent* tor; - tr_sessionLock(session); - tor = tr_torrentFindFromHash(session, info_hash); + tr_sessionLock(session_); + tor = tr_torrentFindFromHash(session_, info_hash); if (tor != NULL && tr_torrentAllowsDHT(tor)) { @@ -670,11 +670,11 @@ static void callback(void* ignore UNUSED, int event, unsigned char const* info_h tr_logAddTorDbg(tor, "Learned %d %s peers from DHT", (int)n, event == DHT_EVENT_VALUES6 ? "IPv6" : "IPv4"); } - tr_sessionUnlock(session); + tr_sessionUnlock(session_); } else if (event == DHT_EVENT_SEARCH_DONE || event == DHT_EVENT_SEARCH_DONE6) { - tr_torrent* tor = tr_torrentFindFromHash(session, info_hash); + tr_torrent* tor = tr_torrentFindFromHash(session_, info_hash); if (tor != NULL) { @@ -714,7 +714,7 @@ static int tr_dhtAnnounce(tr_torrent* tor, int af, bool announce) if (status >= TR_DHT_POOR) { - rc = dht_search(tor->info.hash, announce ? tr_sessionGetPeerPort(session) : 0, af, callback, NULL); + rc = dht_search(tor->info.hash, announce ? tr_sessionGetPeerPort(session_) : 0, af, callback, NULL); if (rc >= 1) { @@ -779,7 +779,7 @@ void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, sockl { TR_ASSERT(tr_isSession(sv)); - if (sv != session) + if (sv != session_) { return; } @@ -808,7 +808,7 @@ void tr_dhtCallback(unsigned char* buf, int buflen, struct sockaddr* from, sockl /* Being slightly late is fine, and has the added benefit of adding some jitter. */ - tr_timerAdd(dht_timer, tosleep, tr_rand_int_weak(1000000)); + tr_timerAdd(dht_timer, (int)tosleep, tr_rand_int_weak(1000000)); } static void timer_callback(evutil_socket_t s UNUSED, short type UNUSED, void* session) diff --git a/libtransmission/tr-getopt-test.c b/libtransmission/tr-getopt-test.c deleted file mode 100644 index c2416a0d5..000000000 --- a/libtransmission/tr-getopt-test.c +++ /dev/null @@ -1,184 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include "transmission.h" -#include "tr-getopt.h" - -#include "libtransmission-test.h" - -static struct tr_option const options[] = -{ - { 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, NULL }, - { 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "" }, - { 's', "piecesize", "Set how many KiB each piece should be, overriding the preferred default", "s", true, "" }, - { 'c', "comment", "Add a comment", "c", true, "" }, - { 't', "tracker", "Add a tracker's announce URL", "t", true, "" }, - { 'q', "pooka", "Pooka", "pk", false, NULL }, - { 'V', "version", "Show version number and exit", "V", false, NULL }, - { 0, NULL, NULL, NULL, false, NULL } -}; - -static int run_test(int argc, char const** argv, int expected_n, int* expected_c, char const** expected_optarg) -{ - int c; - int n; - char const* optarg; - - n = 0; - tr_optind = 1; - - while ((c = tr_getopt("summary", argc, argv, options, &optarg)) != TR_OPT_DONE) - { - check_int(n, <, expected_n); - check_int(c, ==, expected_c[n]); - check_str(optarg, ==, expected_optarg[n]); - ++n; - } - - check_int(n, ==, expected_n); - return 0; -} - -/*** -**** -***/ - -static int test_no_options(void) -{ - int argc = 1; - char const* argv[] = { "/some/path/tr-getopt-test" }; - int expected_n = 0; - int expected_c[] = { 0 }; - char const* expected_optarg[] = { NULL }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_short_noarg(void) -{ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "-p" }; - int expected_n = 1; - int expected_c[] = { 'p' }; - char const* expected_optarg[] = { NULL }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_long_noarg(void) -{ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "--private" }; - int expected_n = 1; - int expected_c[] = { 'p' }; - char const* expected_optarg[] = { NULL }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_short_with_arg(void) -{ - int argc = 3; - char const* argv[] = { "/some/path/tr-getopt-test", "-o", "/tmp/outfile" }; - int expected_n = 1; - int expected_c[] = { 'o' }; - char const* expected_optarg[] = { "/tmp/outfile" }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_long_with_arg(void) -{ - int argc = 3; - char const* argv[] = { "/some/path/tr-getopt-test", "--outfile", "/tmp/outfile" }; - int expected_n = 1; - int expected_c[] = { 'o' }; - char const* expected_optarg[] = { "/tmp/outfile" }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_short_with_arg_after_eq(void) -{ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "-o=/tmp/outfile" }; - int expected_n = 1; - int expected_c[] = { 'o' }; - char const* expected_optarg[] = { "/tmp/outfile" }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_long_with_arg_after_eq(void) -{ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "--outfile=/tmp/outfile" }; - int expected_n = 1; - int expected_c[] = { 'o' }; - char const* expected_optarg[] = { "/tmp/outfile" }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_unknown_option(void) -{ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "-z" }; - int expected_n = 1; - int expected_c[] = { TR_OPT_UNK }; - char const* expected_optarg[] = { "-z" }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_missing_arg(void) -{ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "-o" }; - int expected_n = 1; - int expected_c[] = { TR_OPT_ERR }; - char const* expected_optarg[] = { NULL }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_lots_of_options(void) -{ - int argc = 6; - char const* argv[] = { "/some/path/tr-getopt-test", "--piecesize=4", "-c", "hello world", "-p", "--tracker=foo" }; - int expected_n = 4; - int expected_c[] = { 's', 'c', 'p', 't' }; - char const* expected_optarg[] = { "4", "hello world", NULL, "foo" }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -static int test_match_longer_key(void) -{ - /* confirm that this resolves to 'q' and not 'p' */ - int argc = 2; - char const* argv[] = { "/some/path/tr-getopt-test", "-pk" }; - int expected_n = 1; - int expected_c[] = { 'q' }; - char const* expected_optarg[] = { NULL }; - return run_test(argc, argv, expected_n, expected_c, expected_optarg); -} - -/*** -**** -***/ - -int main(void) -{ - testFunc const tests[] = - { - test_no_options, - test_short_noarg, - test_long_noarg, - test_short_with_arg, - test_long_with_arg, - test_short_with_arg_after_eq, - test_long_with_arg_after_eq, - test_unknown_option, - test_missing_arg, - test_match_longer_key, - test_lots_of_options - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/tr-getopt.h b/libtransmission/tr-getopt.h index b29c1911e..d4325b2eb 100644 --- a/libtransmission/tr-getopt.h +++ b/libtransmission/tr-getopt.h @@ -8,10 +8,9 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS /** * @addtogroup utils Utilities @@ -52,8 +51,6 @@ int tr_getopt(char const* summary, int argc, char const* const* argv, tr_option /** @brief prints the `Usage' help section to stdout */ void tr_getopt_usage(char const* appName, char const* description, tr_option const* opts); -#ifdef __cplusplus -} /* extern "C" */ -#endif - /** @} */ + +TR_END_DECLS diff --git a/libtransmission/tr-macros.h b/libtransmission/tr-macros.h index 237b4d054..9ba0f4621 100644 --- a/libtransmission/tr-macros.h +++ b/libtransmission/tr-macros.h @@ -173,3 +173,12 @@ #define TR_INET6_ADDRSTRLEN 46 #define TR_BAD_SIZE ((size_t)-1) + +/* Guard C code in headers, while including them from C++ */ +#ifdef __cplusplus +#define TR_BEGIN_DECLS extern "C" { +#define TR_END_DECLS } +#else +#define TR_BEGIN_DECLS +#define TR_END_DECLS +#endif diff --git a/libtransmission/tr-udp.c b/libtransmission/tr-udp.c index f04ab87b9..e5c3a137c 100644 --- a/libtransmission/tr-udp.c +++ b/libtransmission/tr-udp.c @@ -32,6 +32,7 @@ THE SOFTWARE. #include +#include #include #include "transmission.h" diff --git a/libtransmission/tr-utp.c b/libtransmission/tr-utp.c index 06db2fd4a..9321a3c33 100644 --- a/libtransmission/tr-utp.c +++ b/libtransmission/tr-utp.c @@ -23,6 +23,7 @@ THE SOFTWARE. #include +#include #include #include "transmission.h" @@ -36,12 +37,12 @@ THE SOFTWARE. #include "tr-utp.h" #include "utils.h" +#ifndef WITH_UTP + #define MY_NAME "UTP" #define dbgmsg(...) tr_logAddDeepNamed(MY_NAME, __VA_ARGS__) -#ifndef WITH_UTP - void UTP_Close(struct UTPSocket* socket) { tr_logAddNamedError(MY_NAME, "UTP_Close(%p) was called.", socket); diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index f5e82129c..8a6c74c9d 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -14,11 +14,6 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif - /*** **** **** Basic Types @@ -32,6 +27,8 @@ extern "C" #include "tr-macros.h" +TR_BEGIN_DECLS + typedef uint32_t tr_file_index_t; typedef uint32_t tr_piece_index_t; /* assuming a 16 KiB block, a 32-bit block index gives us a maximum torrent size of 63 TiB. @@ -721,6 +718,7 @@ void tr_sessionSetTorrentDoneScript(tr_session*, char const* scriptFilename); typedef enum { + TR_LOG_SILENT = 0, TR_LOG_ERROR = 1, TR_LOG_INFO = 2, TR_LOG_DEBUG = 3, @@ -1888,6 +1886,4 @@ static inline bool tr_isDirection(tr_direction d) return d == TR_UP || d == TR_DOWN; } -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/trevent.h b/libtransmission/trevent.h index bf11ad171..3f8464d02 100644 --- a/libtransmission/trevent.h +++ b/libtransmission/trevent.h @@ -12,8 +12,9 @@ #error only libtransmission should #include this header. #endif -/** -**/ +#include "tr-macros.h" + +TR_BEGIN_DECLS void tr_eventInit(tr_session*); @@ -22,3 +23,5 @@ void tr_eventClose(tr_session*); bool tr_amInEventThread(tr_session const*); void tr_runInEventThread(tr_session*, void (* func)(void*), void* user_data); + +TR_END_DECLS diff --git a/libtransmission/utils-test.c b/libtransmission/utils-test.c deleted file mode 100644 index b93307a9f..000000000 --- a/libtransmission/utils-test.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* INT_MAX */ -#include /* sqrt() */ -#include /* strlen() */ -#include /* setenv(), unsetenv() */ - -#ifdef _WIN32 -#include -#define setenv(key, value, unused) SetEnvironmentVariableA(key, value) -#define unsetenv(key) SetEnvironmentVariableA(key, NULL) -#endif - -#include "transmission.h" -#include "ConvertUTF.h" /* tr_utf8_validate*/ -#include "platform.h" -#include "crypto-utils.h" /* tr_rand_int_weak */ -#include "utils.h" -#include "web.h" - -#define SPEED_TEST 0 - -#if SPEED_TEST -#define VERBOSE -#endif - -#include "libtransmission-test.h" - -static int test_strip_positional_args(void) -{ - char const* in; - char const* out; - char const* expected; - - in = "Hello %1$s foo %2$.*f"; - expected = "Hello %s foo %.*f"; - out = tr_strip_positional_args(in); - check_str(out, ==, expected); - - in = "Hello %1$'d foo %2$'f"; - expected = "Hello %d foo %f"; - out = tr_strip_positional_args(in); - check_str(out, ==, expected); - - return 0; -} - -static int test_strstrip(void) -{ - char* in; - char* out; - - /* strstrip */ - in = tr_strdup(" test "); - out = tr_strstrip(in); - check_ptr(in, ==, out); - check_str(out, ==, "test"); - tr_free(in); - - /* strstrip */ - in = tr_strdup(" test test "); - out = tr_strstrip(in); - check_ptr(in, ==, out); - check_str(out, ==, "test test"); - tr_free(in); - - /* strstrip */ - in = tr_strdup("test"); - out = tr_strstrip(in); - check_ptr(in, ==, out); - check_str(out, ==, "test"); - tr_free(in); - - return 0; -} - -static int test_strjoin(void) -{ - char* out; - - char const* in1[] = { "one", "two" }; - out = tr_strjoin(in1, 2, ", "); - check_str(out, ==, "one, two"); - tr_free(out); - - char const* in2[] = { "hello" }; - out = tr_strjoin(in2, 1, "###"); - check_str(out, ==, "hello"); - tr_free(out); - - char const* in3[] = { "a", "b", "ccc", "d", "eeeee" }; - out = tr_strjoin(in3, 5, " "); - check_str(out, ==, "a b ccc d eeeee"); - tr_free(out); - - char const* in4[] = { "7", "ate", "9" }; - out = tr_strjoin(in4, 3, ""); - check_str(out, ==, "7ate9"); - tr_free(out); - - char const** in5; - out = tr_strjoin(in5, 0, "a"); - check_str(out, ==, ""); - tr_free(out); - - return 0; -} - -static int test_buildpath(void) -{ - char* out; - - out = tr_buildPath("foo", "bar", NULL); - check_str(out, ==, "foo" TR_PATH_DELIMITER_STR "bar"); - tr_free(out); - - out = tr_buildPath("", "foo", "bar", NULL); - check_str(out, ==, TR_PATH_DELIMITER_STR "foo" TR_PATH_DELIMITER_STR "bar"); - tr_free(out); - - return 0; -} - -static int test_utf8(void) -{ - char const* in; - char* out; - - in = "hello world"; - out = tr_utf8clean(in, TR_BAD_SIZE); - check_str(out, ==, in); - tr_free(out); - - in = "hello world"; - out = tr_utf8clean(in, 5); - check_str(out, ==, "hello"); - tr_free(out); - - /* this version is not utf-8 (but cp866) */ - in = "\x92\xE0\xE3\xA4\xAD\xAE \xA1\xEB\xE2\xEC \x81\xAE\xA3\xAE\xAC"; - out = tr_utf8clean(in, 17); - check_ptr(out, !=, NULL); - check(strlen(out) == 17 || strlen(out) == 33); - check(tr_utf8_validate(out, TR_BAD_SIZE, NULL)); - tr_free(out); - - /* same string, but utf-8 clean */ - in = "Трудно быть Богом"; - out = tr_utf8clean(in, TR_BAD_SIZE); - check_ptr(out, !=, NULL); - check(tr_utf8_validate(out, TR_BAD_SIZE, NULL)); - check_str(out, ==, in); - tr_free(out); - - in = "\xF4\x00\x81\x82"; - out = tr_utf8clean(in, 4); - check_ptr(out, !=, NULL); - check(strlen(out) == 1 || strlen(out) == 2); - check(tr_utf8_validate(out, TR_BAD_SIZE, NULL)); - tr_free(out); - - in = "\xF4\x33\x81\x82"; - out = tr_utf8clean(in, 4); - check_ptr(out, !=, NULL); - check(strlen(out) == 4 || strlen(out) == 7); - check(tr_utf8_validate(out, TR_BAD_SIZE, NULL)); - tr_free(out); - - return 0; -} - -static int test_numbers(void) -{ - int count; - int* numbers; - - numbers = tr_parseNumberRange("1-10,13,16-19", TR_BAD_SIZE, &count); - check_int(count, ==, 15); - check_int(numbers[0], ==, 1); - check_int(numbers[5], ==, 6); - check_int(numbers[9], ==, 10); - check_int(numbers[10], ==, 13); - check_int(numbers[11], ==, 16); - check_int(numbers[14], ==, 19); - tr_free(numbers); - - numbers = tr_parseNumberRange("1-5,3-7,2-6", TR_BAD_SIZE, &count); - check_int(count, ==, 7); - check_ptr(numbers, !=, NULL); - - for (int i = 0; i < count; ++i) - { - check_int(numbers[i], ==, i + 1); - } - - tr_free(numbers); - - numbers = tr_parseNumberRange("1-Hello", TR_BAD_SIZE, &count); - check_int(count, ==, 0); - check_ptr(numbers, ==, NULL); - - numbers = tr_parseNumberRange("1-", TR_BAD_SIZE, &count); - check_int(count, ==, 0); - check_ptr(numbers, ==, NULL); - - numbers = tr_parseNumberRange("Hello", TR_BAD_SIZE, &count); - check_int(count, ==, 0); - check_ptr(numbers, ==, NULL); - - return 0; -} - -static int compareInts(void const* va, void const* vb) -{ - int const a = *(int const*)va; - int const b = *(int const*)vb; - return a - b; -} - -static int test_lowerbound(void) -{ - int const A[] = { 1, 2, 3, 3, 3, 5, 8 }; - int const expected_pos[] = { 0, 1, 2, 5, 5, 6, 6, 6, 7, 7 }; - bool const expected_exact[] = { true, true, true, false, true, false, false, true, false, false }; - int const N = TR_N_ELEMENTS(A); - - for (int i = 1; i <= 10; i++) - { - bool exact; - int const pos = tr_lowerBound(&i, A, N, sizeof(int), compareInts, &exact); - -#if 0 - - fprintf(stderr, "searching for %d. ", i); - fprintf(stderr, "result: index = %d, ", pos); - - if (pos != N) - { - fprintf(stderr, "A[%d] == %d\n", pos, A[pos]); - } - else - { - fprintf(stderr, "which is off the end.\n"); - } - -#endif - - check_int(pos, ==, expected_pos[i - 1]); - check_int(exact, ==, expected_exact[i - 1]); - } - - return 0; -} - -static int test_quickFindFirst_Iteration(size_t const k, size_t const n, int* buf, int range) -{ - int highest_low; - int lowest_high; - - /* populate buf with random ints */ - for (size_t i = 0; i < n; ++i) - { - buf[i] = tr_rand_int_weak(range); - } - - /* find the best k */ - tr_quickfindFirstK(buf, n, sizeof(int), compareInts, k); - - /* confirm that the smallest K ints are in the first slots K slots in buf */ - - highest_low = INT_MIN; - - for (size_t i = 0; i < k; ++i) - { - if (highest_low < buf[i]) - { - highest_low = buf[i]; - } - } - - lowest_high = INT_MAX; - - for (size_t i = k; i < n; ++i) - { - if (lowest_high > buf[i]) - { - lowest_high = buf[i]; - } - } - - check_int(highest_low, <=, lowest_high); - - return 0; -} - -static int test_quickfindFirst(void) -{ - size_t const k = 10; - size_t const n = 100; - size_t const n_trials = 1000; - int* buf = tr_new(int, n); - - for (size_t i = 0; i < n_trials; ++i) - { - check_int(test_quickFindFirst_Iteration(k, n, buf, 100), ==, 0); - } - - tr_free(buf); - return 0; -} - -static int test_memmem(void) -{ - char const haystack[12] = "abcabcabcabc"; - char const needle[3] = "cab"; - - check_ptr(tr_memmem(haystack, sizeof(haystack), haystack, sizeof(haystack)), ==, haystack); - check_ptr(tr_memmem(haystack, sizeof(haystack), needle, sizeof(needle)), ==, haystack + 2); - check_ptr(tr_memmem(needle, sizeof(needle), haystack, sizeof(haystack)), ==, NULL); - - return 0; -} - -static int test_hex(void) -{ - char hex1[41]; - char hex2[41]; - uint8_t binary[20]; - - memcpy(hex1, "fb5ef5507427b17e04b69cef31fa3379b456735a", 41); - tr_hex_to_binary(hex1, binary, 20); - tr_binary_to_hex(binary, hex2, 20); - check_str(hex1, ==, hex2); - - return 0; -} - -static int test_array(void) -{ - size_t array[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - size_t n = TR_N_ELEMENTS(array); - - tr_removeElementFromArray(array, 5U, sizeof(size_t), n); - --n; - - for (size_t i = 0; i < n; ++i) - { - check_int(array[i], ==, i < 5 ? i : i + 1); - } - - tr_removeElementFromArray(array, 0U, sizeof(size_t), n); - --n; - - for (size_t i = 0; i < n; ++i) - { - check_int(array[i], ==, i < 4 ? i + 1 : i + 2); - } - - tr_removeElementFromArray(array, n - 1, sizeof(size_t), n); - --n; - - for (size_t i = 0; i < n; ++i) - { - check_int(array[i], ==, i < 4 ? i + 1 : i + 2); - } - - return 0; -} - -static int test_url(void) -{ - int port; - char* scheme; - char* host; - char* path; - char* str; - char const* url; - - url = "http://1"; - check(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); - check_str(scheme, ==, "http"); - check_str(host, ==, "1"); - check_str(path, ==, "/"); - check_int(port, ==, 80); - tr_free(scheme); - tr_free(path); - tr_free(host); - - url = "http://www.some-tracker.org/some/path"; - check(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); - check_str(scheme, ==, "http"); - check_str(host, ==, "www.some-tracker.org"); - check_str(path, ==, "/some/path"); - check_int(port, ==, 80); - tr_free(scheme); - tr_free(path); - tr_free(host); - - url = "http://www.some-tracker.org:80/some/path"; - check(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); - check_str(scheme, ==, "http"); - check_str(host, ==, "www.some-tracker.org"); - check_str(path, ==, "/some/path"); - check_int(port, ==, 80); - tr_free(scheme); - tr_free(path); - tr_free(host); - - url = "http%3A%2F%2Fwww.example.com%2F~user%2F%3Ftest%3D1%26test1%3D2"; - str = tr_http_unescape(url, strlen(url)); - check_str(str, ==, "http://www.example.com/~user/?test=1&test1=2"); - tr_free(str); - - return 0; -} - -static int test_truncd(void) -{ - char buf[32]; - double const nan = sqrt(-1); - - tr_snprintf(buf, sizeof(buf), "%.2f%%", 99.999); - check_str(buf, ==, "100.00%"); - - tr_snprintf(buf, sizeof(buf), "%.2f%%", tr_truncd(99.999, 2)); - check_str(buf, ==, "99.99%"); - - tr_snprintf(buf, sizeof(buf), "%.4f", tr_truncd(403650.656250, 4)); - check_str(buf, ==, "403650.6562"); - - tr_snprintf(buf, sizeof(buf), "%.2f", tr_truncd(2.15, 2)); - check_str(buf, ==, "2.15"); - - tr_snprintf(buf, sizeof(buf), "%.2f", tr_truncd(2.05, 2)); - check_str(buf, ==, "2.05"); - - tr_snprintf(buf, sizeof(buf), "%.2f", tr_truncd(3.3333, 2)); - check_str(buf, ==, "3.33"); - - tr_snprintf(buf, sizeof(buf), "%.0f", tr_truncd(3.3333, 0)); - check_str(buf, ==, "3"); - - tr_snprintf(buf, sizeof(buf), "%.0f", tr_truncd(3.9999, 0)); - check_str(buf, ==, "3"); - -#if !(defined(_MSC_VER) || (defined(__MINGW32__) && defined(__MSVCRT__))) - /* FIXME: MSCVRT behaves differently in case of nan */ - tr_snprintf(buf, sizeof(buf), "%.2f", tr_truncd(nan, 2)); - check(strstr(buf, "nan") != NULL || strstr(buf, "NaN") != NULL); -#else - (void)nan; -#endif - - return 0; -} - -static char* test_strdup_printf_valist(char const* fmt, ...) TR_GNUC_PRINTF(1, 2); - -static char* test_strdup_printf_valist(char const* fmt, ...) -{ - va_list args; - char* ret; - - va_start(args, fmt); - ret = tr_strdup_vprintf(fmt, args); - va_end(args); - - return ret; -} - -static int test_strdup_printf(void) -{ - char* s; - char* s2; - char* s3; - - s = tr_strdup_printf("%s", "test"); - check_str(s, ==, "test"); - tr_free(s); - - s = tr_strdup_printf("%d %s %c %u", -1, "0", '1', 2); - check_str(s, ==, "-1 0 1 2"); - tr_free(s); - - s3 = tr_malloc0(4098); - memset(s3, '-', 4097); - s3[2047] = 't'; - s3[2048] = 'e'; - s3[2049] = 's'; - s3[2050] = 't'; - - s2 = tr_malloc0(4096); - memset(s2, '-', 4095); - s2[2047] = '%'; - s2[2048] = 's'; - - s = tr_strdup_printf(s2, "test"); - check_str(s, ==, s3); - tr_free(s); - - tr_free(s2); - - s = tr_strdup_printf("%s", s3); - check_str(s, ==, s3); - tr_free(s); - - tr_free(s3); - - s = test_strdup_printf_valist("\n-%s-%s-%s-\n", "\r", "\t", "\b"); - check_str(s, ==, "\n-\r-\t-\b-\n"); - tr_free(s); - - return 0; -} - -static int test_env(void) -{ - char const* test_key = "TR_TEST_ENV"; - int x; - char* s; - - unsetenv(test_key); - - check(!tr_env_key_exists(test_key)); - x = tr_env_get_int(test_key, 123); - check_int(x, ==, 123); - s = tr_env_get_string(test_key, NULL); - check_str(s, ==, NULL); - s = tr_env_get_string(test_key, "a"); - check_str(s, ==, "a"); - tr_free(s); - - setenv(test_key, "", 1); - - check(tr_env_key_exists(test_key)); - x = tr_env_get_int(test_key, 456); - check_int(x, ==, 456); - s = tr_env_get_string(test_key, NULL); - check_str(s, ==, ""); - tr_free(s); - s = tr_env_get_string(test_key, "b"); - check_str(s, ==, ""); - tr_free(s); - - setenv(test_key, "135", 1); - - check(tr_env_key_exists(test_key)); - x = tr_env_get_int(test_key, 789); - check_int(x, ==, 135); - s = tr_env_get_string(test_key, NULL); - check_str(s, ==, "135"); - tr_free(s); - s = tr_env_get_string(test_key, "c"); - check_str(s, ==, "135"); - tr_free(s); - - return 0; -} - -int main(void) -{ - testFunc const tests[] = - { - test_array, - test_buildpath, - test_hex, - test_lowerbound, - test_quickfindFirst, - test_memmem, - test_numbers, - test_strip_positional_args, - test_strdup_printf, - test_strstrip, - test_strjoin, - test_truncd, - test_url, - test_utf8, - test_env - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/utils.c b/libtransmission/utils.c index 182bc6483..0fe5ac809 100644 --- a/libtransmission/utils.c +++ b/libtransmission/utils.c @@ -702,7 +702,7 @@ void tr_wait_msec(long int msec) **** ***/ -int tr_snprintf(char* buf, size_t buflen, char const* fmt, ...) +int tr_snprintf(void* buf, size_t buflen, char const* fmt, ...) { int len; va_list args; @@ -718,7 +718,7 @@ int tr_snprintf(char* buf, size_t buflen, char const* fmt, ...) * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen (src); if retval >= siz, truncation occurred. */ -size_t tr_strlcpy(char* dst, void const* src, size_t siz) +size_t tr_strlcpy(void* dst, void const* src, size_t siz) { TR_ASSERT(dst != NULL); TR_ASSERT(src != NULL); @@ -787,35 +787,39 @@ double tr_getRatio(uint64_t numerator, uint64_t denominator) return ratio; } -void tr_binary_to_hex(void const* input, char* output, size_t byte_length) +void tr_binary_to_hex(void const* vinput, void* voutput, size_t byte_length) { static char const hex[] = "0123456789abcdef"; - uint8_t const* input_octets = input; + + uint8_t const* input = vinput; + char* output = voutput; /* go from back to front to allow for in-place conversion */ - input_octets += byte_length; + input += byte_length; output += byte_length * 2; *output = '\0'; while (byte_length-- > 0) { - unsigned int const val = *(--input_octets); + unsigned int const val = *(--input); *(--output) = hex[val & 0xf]; *(--output) = hex[val >> 4]; } } -void tr_hex_to_binary(char const* input, void* output, size_t byte_length) +void tr_hex_to_binary(void const* vinput, void* voutput, size_t byte_length) { static char const hex[] = "0123456789abcdef"; - uint8_t* output_octets = output; + + uint8_t const* input = (uint8_t const*)vinput; + uint8_t* output = voutput; for (size_t i = 0; i < byte_length; ++i) { int const hi = strchr(hex, tolower(*input++)) - hex; int const lo = strchr(hex, tolower(*input++)) - hex; - *output_octets++ = (uint8_t)((hi << 4) | lo); + *output++ = (uint8_t)((hi << 4) | lo); } } @@ -1020,7 +1024,7 @@ bool tr_urlParse(char const* url, size_t url_len, char** setme_scheme, char** se **** ***/ -void tr_removeElementFromArray(void* array, unsigned int index_to_remove, size_t sizeof_element, size_t nmemb) +void tr_removeElementFromArray(void* array, size_t index_to_remove, size_t sizeof_element, size_t nmemb) { char* a = array; diff --git a/libtransmission/utils.h b/libtransmission/utils.h index ac1f5c3f3..935114d10 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -13,10 +13,9 @@ #include /* size_t */ #include /* time_t */ -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS /*** **** @@ -229,10 +228,10 @@ char* tr_strdup_printf(char const* fmt, ...) TR_GNUC_MALLOC TR_GNUC_PRINTF(1, 2) char* tr_strdup_vprintf(char const* fmt, va_list args) TR_GNUC_MALLOC TR_GNUC_PRINTF(1, 0); /** @brief Portability wrapper for strlcpy() that uses the system implementation if available */ -size_t tr_strlcpy(char* dst, void const* src, size_t siz); +size_t tr_strlcpy(void* dst, void const* src, size_t siz); /** @brief Portability wrapper for snprintf() that uses the system implementation if available */ -int tr_snprintf(char* buf, size_t buflen, char const* fmt, ...) TR_GNUC_PRINTF(3, 4) TR_GNUC_NONNULL(1, 3); +int tr_snprintf(void* buf, size_t buflen, char const* fmt, ...) TR_GNUC_PRINTF(3, 4) TR_GNUC_NONNULL(1, 3); /** @brief Convenience wrapper around strerorr() guaranteed to not return NULL @param errnum the error number to describe */ @@ -263,8 +262,8 @@ char* tr_strjoin(char const* const* arr, size_t len, char const* delim); int compareInt(void const* va, void const* vb); -void tr_binary_to_hex(void const* input, char* output, size_t byte_length) TR_GNUC_NONNULL(1, 2); -void tr_hex_to_binary(char const* input, void* output, size_t byte_length) TR_GNUC_NONNULL(1, 2); +void tr_binary_to_hex(void const* input, void* output, size_t byte_length) TR_GNUC_NONNULL(1, 2); +void tr_hex_to_binary(void const* input, void* output, size_t byte_length) TR_GNUC_NONNULL(1, 2); /** @brief convenience function to determine if an address is an IP address (IPv4 or IPv6) */ bool tr_addressIsIP(char const* address); @@ -334,7 +333,7 @@ int tr_gettimeofday(struct timeval* tv); bool tr_moveFile(char const* oldpath, char const* newpath, struct tr_error** error) TR_GNUC_NONNULL(1, 2); /** @brief convenience function to remove an item from an array */ -void tr_removeElementFromArray(void* array, unsigned int index_to_remove, size_t sizeof_element, size_t nmemb); +void tr_removeElementFromArray(void* array, size_t index_to_remove, size_t sizeof_element, size_t nmemb); /*** **** @@ -389,6 +388,7 @@ extern unsigned int tr_size_K; /* format a speed from KBps into a user-readable string. */ char* tr_formatter_speed_KBps(char* buf, double KBps, size_t buflen); +// FIXME(ckerr): bytes should be a size_t /* format a memory size from bytes into a user-readable string. */ char* tr_formatter_mem_B(char* buf, int64_t bytes, size_t buflen); @@ -398,6 +398,7 @@ static inline char* tr_formatter_mem_MB(char* buf, double MBps, size_t buflen) return tr_formatter_mem_B(buf, (int64_t)(MBps * tr_mem_K * tr_mem_K), buflen); } +// FIXME(ckerr): bytes should be a size_t /* format a file size from bytes into a user-readable string. */ char* tr_formatter_size_B(char* buf, int64_t bytes, size_t buflen); @@ -422,12 +423,6 @@ char* tr_env_get_string(char const* key, char const* default_value); void tr_net_init(void); -/*** -**** -***/ - -#ifdef __cplusplus -} -#endif - /** @} */ + +TR_END_DECLS diff --git a/libtransmission/variant-benc.c b/libtransmission/variant-benc.c index 853659074..bdd2bfe3b 100644 --- a/libtransmission/variant-benc.c +++ b/libtransmission/variant-benc.c @@ -15,7 +15,7 @@ #include "ConvertUTF.h" -#define __LIBTRANSMISSION_VARIANT_MODULE__ +#define LIBTRANSMISSION_VARIANT_MODULE #include "transmission.h" #include "ptrarray.h" @@ -39,8 +39,10 @@ * but to handle it as a signed 64bit integer is mandatory to handle * "large files" aka .torrent for more that 4Gbyte */ -int tr_bencParseInt(uint8_t const* buf, uint8_t const* bufend, uint8_t const** setme_end, int64_t* setme_val) +int tr_bencParseInt(void const* vbuf, void const* vbufend, uint8_t const** setme_end, int64_t* setme_val) { + uint8_t const* const buf = (uint8_t const*)vbuf; + uint8_t const* const bufend = (uint8_t const*)vbufend; char* endptr; void const* begin; void const* end; @@ -88,9 +90,12 @@ int tr_bencParseInt(uint8_t const* buf, uint8_t const* bufend, uint8_t const** s * Note that there is no constant beginning delimiter, and no ending delimiter. * Example: 4:spam represents the string "spam" */ -int tr_bencParseStr(uint8_t const* buf, uint8_t const* bufend, uint8_t const** setme_end, uint8_t const** setme_str, +int tr_bencParseStr(void const* vbuf, void const* vbufend, uint8_t const** setme_end, uint8_t const** setme_str, size_t* setme_strlen) { + uint8_t const* const buf = (uint8_t const*)vbuf; + uint8_t const* const bufend = (uint8_t const*)vbufend; + void const* end; size_t len; char* ulend; diff --git a/libtransmission/variant-common.h b/libtransmission/variant-common.h index 8e10634d1..d0d8b8bd1 100644 --- a/libtransmission/variant-common.h +++ b/libtransmission/variant-common.h @@ -8,10 +8,14 @@ #pragma once -#ifndef __LIBTRANSMISSION_VARIANT_MODULE__ +#ifndef LIBTRANSMISSION_VARIANT_MODULE #error only libtransmission/variant-*.c should #include this header. #endif +#include "tr-macros.h" + +TR_BEGIN_DECLS + typedef void (* VariantWalkFunc)(tr_variant const* val, void* user_data); struct VariantWalkFuncs @@ -37,10 +41,12 @@ void tr_variantInit(tr_variant* v, char type); int tr_jsonParse(char const* source, void const* vbuf, size_t len, tr_variant* setme_benc, char const** setme_end); /** @brief Private function that's exposed here only for unit tests */ -int tr_bencParseInt(uint8_t const* buf, uint8_t const* bufend, uint8_t const** setme_end, int64_t* setme_val); +int tr_bencParseInt(void const* buf, void const* bufend, uint8_t const** setme_end, int64_t* setme_val); /** @brief Private function that's exposed here only for unit tests */ -int tr_bencParseStr(uint8_t const* buf, uint8_t const* bufend, uint8_t const** setme_end, uint8_t const** setme_str, +int tr_bencParseStr(void const* buf, void const* bufend, uint8_t const** setme_end, uint8_t const** setme_str, size_t* setme_strlen); int tr_variantParseBenc(void const* buf, void const* end, tr_variant* top, char const** setme_end); + +TR_END_DECLS diff --git a/libtransmission/variant-json.c b/libtransmission/variant-json.c index 0dc8a50c9..490e515f2 100644 --- a/libtransmission/variant-json.c +++ b/libtransmission/variant-json.c @@ -15,14 +15,11 @@ #include /* evbuffer_add() */ #include /* evutil_strtoll() */ -#define JSONSL_STATE_USER_FIELDS /* no fields */ -#include "jsonsl.h" -#include "jsonsl.c" - -#define __LIBTRANSMISSION_VARIANT_MODULE__ +#define LIBTRANSMISSION_VARIANT_MODULE #include "transmission.h" #include "ConvertUTF.h" +#include "jsonsl.h" #include "list.h" #include "log.h" #include "ptrarray.h" @@ -246,8 +243,7 @@ static char* extract_escaped_string(char const* in, size_t in_len, size_t* len, if (ConvertUTF32toUTF8(&str32_walk, str32_end, &str8_walk, str8_end, 0) == 0) { - size_t const len = str8_walk - str8_buf; - evbuffer_add(buf, str8_buf, len); + evbuffer_add(buf, str8_buf, str8_walk - str8_buf); unescaped = true; } diff --git a/libtransmission/variant-test.c b/libtransmission/variant-test.c deleted file mode 100644 index 7b7a6f6ad..000000000 --- a/libtransmission/variant-test.c +++ /dev/null @@ -1,598 +0,0 @@ -/* - * This file Copyright (C) 2013-2014 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include /* isspace() */ -#include /* EILSEQ */ -#include /* strlen(), strncmp() */ - -#include - -#define __LIBTRANSMISSION_VARIANT_MODULE__ - -#include "transmission.h" -#include "utils.h" /* tr_free */ -#include "variant.h" -#include "variant-common.h" - -#include "libtransmission-test.h" - -#ifndef _WIN32 -#define STACK_SMASH_DEPTH (1 * 1000 * 1000) -#else -#define STACK_SMASH_DEPTH (100 * 1000) -#endif - -static int testInt(void) -{ - uint8_t buf[128]; - int64_t val; - int err; - uint8_t const* end; - - /* good int string */ - tr_snprintf((char*)buf, sizeof(buf), "i64e"); - err = tr_bencParseInt(buf, buf + 4, &end, &val); - check_int(err, ==, 0); - check_int(val, ==, 64); - check_ptr(end, ==, buf + 4); - - /* missing 'e' */ - end = NULL; - val = 888; - err = tr_bencParseInt(buf, buf + 3, &end, &val); - check_int(err, ==, EILSEQ); - check_int(val, ==, 888); - check_ptr(end, ==, NULL); - - /* empty buffer */ - err = tr_bencParseInt(buf, buf + 0, &end, &val); - check_int(err, ==, EILSEQ); - check_int(val, ==, 888); - check_ptr(end, ==, NULL); - - /* bad number */ - tr_snprintf((char*)buf, sizeof(buf), "i6z4e"); - err = tr_bencParseInt(buf, buf + 5, &end, &val); - check_int(err, ==, EILSEQ); - check_int(val, ==, 888); - check_ptr(end, ==, NULL); - - /* negative number */ - tr_snprintf((char*)buf, sizeof(buf), "i-3e"); - err = tr_bencParseInt(buf, buf + 4, &end, &val); - check_int(err, ==, 0); - check_int(val, ==, -3); - check_ptr(end, ==, buf + 4); - - /* zero */ - tr_snprintf((char*)buf, sizeof(buf), "i0e"); - err = tr_bencParseInt(buf, buf + 4, &end, &val); - check_int(err, ==, 0); - check_int(val, ==, 0); - check_ptr(end, ==, buf + 3); - - /* no leading zeroes allowed */ - val = 0; - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "i04e"); - err = tr_bencParseInt(buf, buf + 4, &end, &val); - check_int(err, ==, EILSEQ); - check_int(val, ==, 0); - check_ptr(end, ==, NULL); - - return 0; -} - -static int testStr(void) -{ - uint8_t buf[128]; - int err; - int n; - uint8_t const* end; - uint8_t const* str; - size_t len; - - /* string len is designed to overflow */ - n = tr_snprintf((char*)buf, sizeof(buf), "%zu:boat", (size_t)(SIZE_MAX - 2)); - err = tr_bencParseStr(buf, buf + n, &end, &str, &len); - check_int(err, ==, EILSEQ); - check_uint(len, ==, 0); - check_ptr(str, ==, NULL); - check_ptr(end, ==, NULL); - - /* good string */ - n = tr_snprintf((char*)buf, sizeof(buf), "4:boat"); - err = tr_bencParseStr(buf, buf + n, &end, &str, &len); - check_int(err, ==, 0); - check_uint(len, ==, 4); - check_mem(str, ==, "boat", len); - check_ptr(end, ==, buf + 6); - str = NULL; - end = NULL; - len = 0; - - /* string goes past end of buffer */ - err = tr_bencParseStr(buf, buf + (n - 1), &end, &str, &len); - check_int(err, ==, EILSEQ); - check_uint(len, ==, 0); - check_ptr(str, ==, NULL); - check_ptr(end, ==, NULL); - - /* empty string */ - n = tr_snprintf((char*)buf, sizeof(buf), "0:"); - err = tr_bencParseStr(buf, buf + n, &end, &str, &len); - check_int(err, ==, 0); - check_uint(len, ==, 0); - check_uint(*str, ==, '\0'); - check_ptr(end, ==, buf + 2); - str = NULL; - end = NULL; - len = 0; - - /* short string */ - n = tr_snprintf((char*)buf, sizeof(buf), "3:boat"); - err = tr_bencParseStr(buf, buf + n, &end, &str, &len); - check_int(err, ==, 0); - check_uint(len, ==, 3); - check_mem(str, ==, "boa", len); - check_ptr(end, ==, buf + 5); - str = NULL; - end = NULL; - len = 0; - - return 0; -} - -static int testString(char const* str, bool isGood) -{ - tr_variant val; - char const* end = NULL; - char* saved; - size_t const len = strlen(str); - size_t savedLen; - int err; - - err = tr_variantFromBencFull(&val, str, len, NULL, &end); - - if (!isGood) - { - check_int(err, !=, 0); - } - else - { - check_int(err, ==, 0); -#if 0 - fprintf(stderr, "in: [%s]\n", str); - fprintf(stderr, "out:\n%s", tr_variantToStr(&val, TR_VARIANT_FMT_JSON, NULL)); -#endif - check_ptr(end, ==, str + len); - saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &savedLen); - check_str(saved, ==, str); - check_uint(savedLen, ==, len); - tr_free(saved); - tr_variantFree(&val); - } - - return 0; -} - -static int testParse(void) -{ - tr_variant val; - tr_variant* child; - tr_variant* child2; - char buf[512]; - char const* end; - int err; - size_t len; - int64_t i; - char* saved; - - tr_snprintf((char*)buf, sizeof(buf), "i64e"); - err = tr_variantFromBencFull(&val, buf, sizeof(buf), NULL, &end); - check_int(err, ==, 0); - check(tr_variantGetInt(&val, &i)); - check_int(i, ==, 64); - check_ptr(end, ==, buf + 4); - tr_variantFree(&val); - - tr_snprintf((char*)buf, sizeof(buf), "li64ei32ei16ee"); - err = tr_variantFromBencFull(&val, buf, sizeof(buf), NULL, &end); - check_int(err, ==, 0); - check_ptr(end, ==, buf + strlen((char*)buf)); - check_uint(val.val.l.count, ==, 3); - check(tr_variantGetInt(&val.val.l.vals[0], &i)); - check_int(i, ==, 64); - check(tr_variantGetInt(&val.val.l.vals[1], &i)); - check_int(i, ==, 32); - check(tr_variantGetInt(&val.val.l.vals[2], &i)); - check_int(i, ==, 16); - saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); - check_str(saved, ==, buf); - tr_free(saved); - tr_variantFree(&val); - - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "lllee"); - err = tr_variantFromBencFull(&val, buf, sizeof(buf), NULL, &end); - check_int(err, !=, 0); - check_ptr(end, ==, NULL); - - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "le"); - err = tr_variantFromBencFull(&val, buf, sizeof(buf), NULL, &end); - check_int(err, ==, 0); - check_ptr(end, ==, buf + 2); - saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); - check_str(saved, ==, "le"); - tr_free(saved); - tr_variantFree(&val); - - if ((err = testString("llleee", true)) != 0) - { - return err; - } - - if ((err = testString("d3:cow3:moo4:spam4:eggse", true)) != 0) - { - return err; - } - - if ((err = testString("d4:spaml1:a1:bee", true)) != 0) - { - return err; - } - - if ((err = testString("d5:greenli1ei2ei3ee4:spamd1:ai123e3:keyi214eee", true)) != 0) - { - return err; - } - - if ((err = testString("d9:publisher3:bob17:publisher-webpage15:www.example.com18:publisher.location4:homee", true)) != 0) - { - return err; - } - - if ((err = testString("d8:completei1e8:intervali1800e12:min intervali1800e5:peers0:e", true)) != 0) - { - return err; - } - - if ((err = testString("d1:ai0e1:be", false)) != 0) /* odd number of children */ - { - return err; - } - - if ((err = testString("", false)) != 0) - { - return err; - } - - if ((err = testString(" ", false)) != 0) - { - return err; - } - - /* nested containers - * parse an unsorted dict - * save as a sorted dict */ - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "lld1:bi32e1:ai64eeee"); - err = tr_variantFromBencFull(&val, buf, sizeof(buf), NULL, &end); - check_int(err, ==, 0); - check_ptr(end, ==, buf + strlen((char const*)buf)); - check_ptr((child = tr_variantListChild(&val, 0)), !=, NULL); - check_ptr((child2 = tr_variantListChild(child, 0)), !=, NULL); - saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); - check_str(saved, ==, "lld1:ai64e1:bi32eeee"); - tr_free(saved); - tr_variantFree(&val); - - /* too many endings */ - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "leee"); - err = tr_variantFromBencFull(&val, buf, sizeof(buf), NULL, &end); - check_int(err, ==, 0); - check_ptr(end, ==, buf + 2); - saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); - check_str(saved, ==, "le"); - tr_free(saved); - tr_variantFree(&val); - - /* no ending */ - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "l1:a1:b1:c"); - err = tr_variantFromBencFull(&val, buf, strlen(buf), NULL, &end); - check_int(err, !=, 0); - - /* incomplete string */ - end = NULL; - tr_snprintf((char*)buf, sizeof(buf), "1:"); - err = tr_variantFromBencFull(&val, buf, strlen(buf), NULL, &end); - check_int(err, !=, 0); - - return 0; -} - -static void stripWhitespace(char* in) -{ - char* out = in; - - for (; !tr_str_is_empty(in); ++in) - { - if (!isspace(*in)) - { - *out++ = *in; - } - } - - *out = '\0'; -} - -static int testJSONSnippet(char const* benc_str, char const* expected) -{ - tr_variant top; - char* serialized; - struct evbuffer* buf; - - tr_variantFromBenc(&top, benc_str, strlen(benc_str)); - buf = tr_variantToBuf(&top, TR_VARIANT_FMT_JSON); - serialized = (char*)evbuffer_pullup(buf, -1); - stripWhitespace(serialized); -#if 0 - fprintf(stderr, "benc: %s\n", benc_str); - fprintf(stderr, "json: %s\n", serialized); - fprintf(stderr, "want: %s\n", expected); -#endif - check_str(serialized, ==, expected); - tr_variantFree(&top); - evbuffer_free(buf); - return 0; -} - -static int testJSON(void) -{ - int val; - char const* benc_str; - char const* expected; - - benc_str = "i6e"; - expected = "6"; - - if ((val = testJSONSnippet(benc_str, expected)) != 0) - { - return val; - } - - benc_str = "d5:helloi1e5:worldi2ee"; - expected = "{\"hello\":1,\"world\":2}"; - - if ((val = testJSONSnippet(benc_str, expected)) != 0) - { - return val; - } - - benc_str = "d5:helloi1e5:worldi2e3:fooli1ei2ei3eee"; - expected = "{\"foo\":[1,2,3],\"hello\":1,\"world\":2}"; - - if ((val = testJSONSnippet(benc_str, expected)) != 0) - { - return val; - } - - benc_str = "d5:helloi1e5:worldi2e3:fooli1ei2ei3ed1:ai0eeee"; - expected = "{\"foo\":[1,2,3,{\"a\":0}],\"hello\":1,\"world\":2}"; - - if ((val = testJSONSnippet(benc_str, expected)) != 0) - { - return val; - } - - benc_str = "d4:argsd6:statusle7:status2lee6:result7:successe"; - expected = "{\"args\":{\"status\":[],\"status2\":[]},\"result\":\"success\"}"; - - if ((val = testJSONSnippet(benc_str, expected)) != 0) - { - return val; - } - - return 0; -} - -static int testMerge(void) -{ - size_t len; - tr_variant dest; - tr_variant src; - int64_t i; - char const* s; - tr_quark const i1 = tr_quark_new("i1", 2); - tr_quark const i2 = tr_quark_new("i2", 2); - tr_quark const i3 = tr_quark_new("i3", 2); - tr_quark const i4 = tr_quark_new("i4", 2); - tr_quark const s5 = tr_quark_new("s5", 2); - tr_quark const s6 = tr_quark_new("s6", 2); - tr_quark const s7 = tr_quark_new("s7", 2); - tr_quark const s8 = tr_quark_new("s8", 2); - - /* initial dictionary (default values) */ - tr_variantInitDict(&dest, 10); - tr_variantDictAddInt(&dest, i1, 1); - tr_variantDictAddInt(&dest, i2, 2); - tr_variantDictAddInt(&dest, i4, -35); /* remains untouched */ - tr_variantDictAddStr(&dest, s5, "abc"); - tr_variantDictAddStr(&dest, s6, "def"); - tr_variantDictAddStr(&dest, s7, "127.0.0.1"); /* remains untouched */ - - /* new dictionary, will overwrite items in dest */ - tr_variantInitDict(&src, 10); - tr_variantDictAddInt(&src, i1, 1); /* same value */ - tr_variantDictAddInt(&src, i2, 4); /* new value */ - tr_variantDictAddInt(&src, i3, 3); /* new key:value */ - tr_variantDictAddStr(&src, s5, "abc"); /* same value */ - tr_variantDictAddStr(&src, s6, "xyz"); /* new value */ - tr_variantDictAddStr(&src, s8, "ghi"); /* new key:value */ - - tr_variantMergeDicts(&dest, /*const*/ &src); - - check(tr_variantDictFindInt(&dest, i1, &i)); - check_int(i, ==, 1); - check(tr_variantDictFindInt(&dest, i2, &i)); - check_int(i, ==, 4); - check(tr_variantDictFindInt(&dest, i3, &i)); - check_int(i, ==, 3); - check(tr_variantDictFindInt(&dest, i4, &i)); - check_int(i, ==, -35); - check(tr_variantDictFindStr(&dest, s5, &s, &len)); - check_uint(len, ==, 3); - check_str(s, ==, "abc"); - check(tr_variantDictFindStr(&dest, s6, &s, &len)); - check_uint(len, ==, 3); - check_str(s, ==, "xyz"); - check(tr_variantDictFindStr(&dest, s7, &s, &len)); - check_uint(len, ==, 9); - check_str(s, ==, "127.0.0.1"); - check(tr_variantDictFindStr(&dest, s8, &s, &len)); - check_uint(len, ==, 3); - check_str(s, ==, "ghi"); - - tr_variantFree(&dest); - tr_variantFree(&src); - return 0; -} - -static int testStackSmash(void) -{ - size_t len; - int err; - char* in; - char const* end; - tr_variant val; - char* saved; - int const depth = STACK_SMASH_DEPTH; - - in = tr_new(char, depth * 2 + 1); - - for (int i = 0; i < depth; ++i) - { - in[i] = 'l'; - in[depth + i] = 'e'; - } - - in[depth * 2] = '\0'; - err = tr_variantFromBencFull(&val, in, depth * 2, NULL, &end); - check_int(err, ==, 0); - check_ptr(end, ==, in + depth * 2); - saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); - check_str(saved, ==, in); - tr_free(in); - tr_free(saved); - tr_variantFree(&val); - - return 0; -} - -static int testBool(void) -{ - tr_variant top; - int64_t intVal; - bool boolVal; - tr_quark const key1 = tr_quark_new("key1", 4); - tr_quark const key2 = tr_quark_new("key2", 4); - tr_quark const key3 = tr_quark_new("key3", 4); - tr_quark const key4 = tr_quark_new("key4", 4); - - tr_variantInitDict(&top, 0); - - tr_variantDictAddBool(&top, key1, false); - tr_variantDictAddBool(&top, key2, 0); - tr_variantDictAddInt(&top, key3, true); - tr_variantDictAddInt(&top, key4, 1); - check(tr_variantDictFindBool(&top, key1, &boolVal)); - check(!boolVal); - check(tr_variantDictFindBool(&top, key2, &boolVal)); - check(!boolVal); - check(tr_variantDictFindBool(&top, key3, &boolVal)); - check(boolVal); - check(tr_variantDictFindBool(&top, key4, &boolVal)); - check(boolVal); - check(tr_variantDictFindInt(&top, key1, &intVal)); - check_int(intVal, ==, 0); - check(tr_variantDictFindInt(&top, key2, &intVal)); - check_int(intVal, ==, 0); - check(tr_variantDictFindInt(&top, key3, &intVal)); - check_int(intVal, !=, 0); - check(tr_variantDictFindInt(&top, key4, &intVal)); - check_int(intVal, !=, 0); - - tr_variantFree(&top); - return 0; -} - -static int testParse2(void) -{ - tr_variant top; - tr_variant top2; - int64_t intVal; - char const* strVal; - double realVal; - bool boolVal; - size_t len; - char* benc; - char const* end; - size_t strLen; - tr_quark const key_bool = tr_quark_new("this-is-a-bool", TR_BAD_SIZE); - tr_quark const key_real = tr_quark_new("this-is-a-real", TR_BAD_SIZE); - tr_quark const key_int = tr_quark_new("this-is-an-int", TR_BAD_SIZE); - tr_quark const key_str = tr_quark_new("this-is-a-string", TR_BAD_SIZE); - - tr_variantInitDict(&top, 0); - tr_variantDictAddBool(&top, key_bool, true); - tr_variantDictAddInt(&top, key_int, 1234); - tr_variantDictAddReal(&top, key_real, 0.5); - tr_variantDictAddStr(&top, key_str, "this-is-a-string"); - - benc = tr_variantToStr(&top, TR_VARIANT_FMT_BENC, &len); - check_str(benc, ==, "d14:this-is-a-booli1e14:this-is-a-real8:0.50000016:this-is-a-string16:this-is-a-string14:this-is-an-" - "inti1234ee"); - check_int(tr_variantFromBencFull(&top2, benc, len, NULL, &end), ==, 0); - check_ptr(end, ==, benc + len); - check(tr_variantIsDict(&top2)); - check(tr_variantDictFindInt(&top, key_int, &intVal)); - check_int(intVal, ==, 1234); - check(tr_variantDictFindBool(&top, key_bool, &boolVal)); - check(boolVal); - check(tr_variantDictFindStr(&top, key_str, &strVal, &strLen)); - check_uint(strLen, ==, 16); - check_str(strVal, ==, "this-is-a-string"); - check(tr_variantDictFindReal(&top, key_real, &realVal)); - check_int((int)(realVal * 100), ==, 50); - - tr_variantFree(&top2); - tr_free(benc); - tr_variantFree(&top); - - return 0; -} - -int main(void) -{ - static testFunc const tests[] = - { - testInt, - testStr, - testParse, - testJSON, - testMerge, - testBool, - testParse2, - testStackSmash - }; - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/variant.c b/libtransmission/variant.c index 8a14001c2..a496afa24 100644 --- a/libtransmission/variant.c +++ b/libtransmission/variant.c @@ -31,7 +31,7 @@ #include -#define __LIBTRANSMISSION_VARIANT_MODULE__ +#define LIBTRANSMISSION_VARIANT_MODULE #include "transmission.h" #include "ConvertUTF.h" @@ -821,13 +821,13 @@ static void nodeDestruct(struct SaveNode* node) * easier to read, but was vulnerable to a smash-stacking * attack via maliciously-crafted data. (#667) */ -void tr_variantWalk(tr_variant const* v, struct VariantWalkFuncs const* walkFuncs, void* user_data, bool sort_dicts) +void tr_variantWalk(tr_variant const* v_in, struct VariantWalkFuncs const* walkFuncs, void* user_data, bool sort_dicts) { int stackSize = 0; int stackAlloc = 64; struct SaveNode* stack = tr_new(struct SaveNode, stackAlloc); - nodeConstruct(&stack[stackSize++], v, sort_dicts); + nodeConstruct(&stack[stackSize++], v_in, sort_dicts); while (stackSize > 0) { diff --git a/libtransmission/variant.h b/libtransmission/variant.h index 0f552545e..34f2f62f2 100644 --- a/libtransmission/variant.h +++ b/libtransmission/variant.h @@ -8,14 +8,13 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif +#include /* int64_t */ -#include /* for int64_t */ +#include "tr-macros.h" #include "quark.h" +TR_BEGIN_DECLS + struct evbuffer; struct tr_error; @@ -280,6 +279,4 @@ void tr_variantMergeDicts(tr_variant* dict_target, tr_variant const* dict_source /* @} */ -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/watchdir-common.h b/libtransmission/watchdir-common.h index c741fea28..b1b8e2e8c 100644 --- a/libtransmission/watchdir-common.h +++ b/libtransmission/watchdir-common.h @@ -8,7 +8,7 @@ #pragma once -#ifndef __LIBTRANSMISSION_WATCHDIR_MODULE__ +#ifndef LIBTRANSMISSION_WATCHDIR_MODULE #error only the libtransmission watchdir module should #include this header. #endif diff --git a/libtransmission/watchdir-generic.c b/libtransmission/watchdir-generic.c index 6edd65aac..c70dcdfc6 100644 --- a/libtransmission/watchdir-generic.c +++ b/libtransmission/watchdir-generic.c @@ -10,7 +10,7 @@ #include -#define __LIBTRANSMISSION_WATCHDIR_MODULE__ +#define LIBTRANSMISSION_WATCHDIR_MODULE #include "transmission.h" #include "log.h" diff --git a/libtransmission/watchdir-inotify.c b/libtransmission/watchdir-inotify.c index 68645ec20..9b87cac84 100644 --- a/libtransmission/watchdir-inotify.c +++ b/libtransmission/watchdir-inotify.c @@ -17,7 +17,7 @@ #include #include -#define __LIBTRANSMISSION_WATCHDIR_MODULE__ +#define LIBTRANSMISSION_WATCHDIR_MODULE #include "transmission.h" #include "log.h" diff --git a/libtransmission/watchdir-kqueue.c b/libtransmission/watchdir-kqueue.c index ea2b6d2a7..bbd19d43d 100644 --- a/libtransmission/watchdir-kqueue.c +++ b/libtransmission/watchdir-kqueue.c @@ -21,7 +21,7 @@ #include -#define __LIBTRANSMISSION_WATCHDIR_MODULE__ +#define LIBTRANSMISSION_WATCHDIR_MODULE #include "transmission.h" #include "log.h" diff --git a/libtransmission/watchdir-test.c b/libtransmission/watchdir-test.c deleted file mode 100644 index 79c444618..000000000 --- a/libtransmission/watchdir-test.c +++ /dev/null @@ -1,400 +0,0 @@ -/* - * This file Copyright (C) 2015-2016 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include - -#include "transmission.h" -#include "file.h" -#include "net.h" -#include "utils.h" -#include "watchdir.h" - -#include "libtransmission-test.h" - -/*** -**** -***/ - -typedef struct callback_data -{ - tr_watchdir_t dir; - char* name; - tr_watchdir_status result; -} -callback_data; - -#define CB_DATA_STATIC_INIT { NULL, NULL, 0 } - -static struct event_base* ev_base = NULL; - -extern struct timeval tr_watchdir_generic_interval; -extern unsigned int tr_watchdir_retry_limit; -extern struct timeval tr_watchdir_retry_start_interval; -extern struct timeval tr_watchdir_retry_max_interval; - -static struct timeval const FIFTY_MSEC = { .tv_sec = 0, .tv_usec = 50000 }; -static struct timeval const ONE_HUNDRED_MSEC = { .tv_sec = 0, .tv_usec = 100000 }; -static struct timeval const TWO_HUNDRED_MSEC = { .tv_sec = 0, .tv_usec = 200000 }; - -static void process_events(void) -{ - event_base_loopexit(ev_base, &TWO_HUNDRED_MSEC); - event_base_dispatch(ev_base); -} - -static tr_watchdir_status callback(tr_watchdir_t dir, char const* name, void* context) -{ - callback_data* const data = context; - - if (data->result != TR_WATCHDIR_RETRY) - { - data->dir = dir; - - if (data->name != NULL) - { - tr_free(data->name); - } - - data->name = tr_strdup(name); - } - - return data->result; -} - -static void reset_callback_data(callback_data* data, tr_watchdir_status result) -{ - tr_free(data->name); - - data->dir = NULL; - data->name = NULL; - data->result = result; -} - -static void create_file(char const* parent_dir, char const* name) -{ - char* const path = tr_buildPath(parent_dir, name, NULL); - libtest_create_file_with_string_contents(path, ""); - tr_free(path); -} - -static void create_dir(char const* parent_dir, char const* name) -{ - char* const path = tr_buildPath(parent_dir, name, NULL); - tr_sys_dir_create(path, 0, 0700, NULL); - tr_free(path); -} - -static tr_watchdir_t create_watchdir(char const* path, tr_watchdir_cb callback, void* callback_user_data, - struct event_base* event_base) -{ -#ifdef WATCHDIR_TEST_FORCE_GENERIC - bool const force_generic = true; -#else - bool const force_generic = false; -#endif - - return tr_watchdir_new(path, callback, callback_user_data, event_base, force_generic); -} - -/*** -**** -***/ - -static int test_construct(void) -{ - char* const test_dir = libtest_sandbox_create(); - tr_watchdir_t wd; - - ev_base = event_base_new(); - - wd = create_watchdir(test_dir, &callback, NULL, ev_base); - check_ptr(wd, !=, NULL); - check(tr_sys_path_is_same(test_dir, tr_watchdir_get_path(wd), NULL)); - - process_events(); - - tr_watchdir_free(wd); - - event_base_free(ev_base); - - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_initial_scan(void) -{ - char* const test_dir = libtest_sandbox_create(); - - ev_base = event_base_new(); - - /* Speed up generic implementation */ - tr_watchdir_generic_interval = ONE_HUNDRED_MSEC; - - { - callback_data wd_data = CB_DATA_STATIC_INIT; - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - - tr_watchdir_t wd = create_watchdir(test_dir, &callback, &wd_data, ev_base); - check_ptr(wd, !=, NULL); - - process_events(); - check_ptr(wd_data.dir, ==, NULL); - check_ptr(wd_data.name, ==, NULL); - - tr_watchdir_free(wd); - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - } - - create_file(test_dir, "test"); - - { - callback_data wd_data = CB_DATA_STATIC_INIT; - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - - tr_watchdir_t wd = create_watchdir(test_dir, &callback, &wd_data, ev_base); - check_ptr(wd, !=, NULL); - - process_events(); - check_ptr(wd_data.dir, ==, wd); - check_str(wd_data.name, ==, "test"); - - tr_watchdir_free(wd); - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - } - - event_base_free(ev_base); - - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_watch(void) -{ - char* const test_dir = libtest_sandbox_create(); - callback_data wd_data = CB_DATA_STATIC_INIT; - tr_watchdir_t wd; - - ev_base = event_base_new(); - - /* Speed up generic implementation */ - tr_watchdir_generic_interval = ONE_HUNDRED_MSEC; - - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - wd = create_watchdir(test_dir, &callback, &wd_data, ev_base); - check_ptr(wd, !=, NULL); - - process_events(); - check_ptr(wd_data.dir, ==, NULL); - check_ptr(wd_data.name, ==, NULL); - - create_file(test_dir, "test"); - - process_events(); - check_ptr(wd_data.dir, ==, wd); - check_str(wd_data.name, ==, "test"); - - reset_callback_data(&wd_data, TR_WATCHDIR_IGNORE); - create_file(test_dir, "test2"); - - process_events(); - check_ptr(wd_data.dir, ==, wd); - check_str(wd_data.name, ==, "test2"); - - reset_callback_data(&wd_data, TR_WATCHDIR_IGNORE); - create_dir(test_dir, "test3"); - - process_events(); - check_ptr(wd_data.dir, ==, NULL); - check_ptr(wd_data.name, ==, NULL); - - tr_watchdir_free(wd); - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - - event_base_free(ev_base); - - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_watch_two_dirs(void) -{ - char* const test_dir = libtest_sandbox_create(); - char* const dir1 = tr_buildPath(test_dir, "a", NULL); - char* const dir2 = tr_buildPath(test_dir, "b", NULL); - callback_data wd1_data = CB_DATA_STATIC_INIT; - callback_data wd2_data = CB_DATA_STATIC_INIT; - tr_watchdir_t wd1; - tr_watchdir_t wd2; - - ev_base = event_base_new(); - - /* Speed up generic implementation */ - tr_watchdir_generic_interval = ONE_HUNDRED_MSEC; - - create_dir(dir1, NULL); - create_dir(dir2, NULL); - - reset_callback_data(&wd1_data, TR_WATCHDIR_ACCEPT); - wd1 = create_watchdir(dir1, &callback, &wd1_data, ev_base); - check_ptr(wd1, !=, NULL); - - reset_callback_data(&wd2_data, TR_WATCHDIR_ACCEPT); - wd2 = create_watchdir(dir2, &callback, &wd2_data, ev_base); - check_ptr(wd2, !=, NULL); - - process_events(); - check_ptr(wd1_data.dir, ==, NULL); - check_ptr(wd1_data.name, ==, NULL); - check_ptr(wd2_data.dir, ==, NULL); - check_ptr(wd2_data.name, ==, NULL); - - create_file(dir1, "test"); - - process_events(); - check_ptr(wd1_data.dir, ==, wd1); - check_str(wd1_data.name, ==, "test"); - check_ptr(wd2_data.dir, ==, NULL); - check_ptr(wd2_data.name, ==, NULL); - - reset_callback_data(&wd1_data, TR_WATCHDIR_ACCEPT); - reset_callback_data(&wd2_data, TR_WATCHDIR_ACCEPT); - create_file(dir2, "test2"); - - process_events(); - check_ptr(wd1_data.dir, ==, NULL); - check_ptr(wd1_data.name, ==, NULL); - check_ptr(wd2_data.dir, ==, wd2); - check_str(wd2_data.name, ==, "test2"); - - reset_callback_data(&wd1_data, TR_WATCHDIR_IGNORE); - reset_callback_data(&wd2_data, TR_WATCHDIR_IGNORE); - create_file(dir1, "test3"); - create_file(dir2, "test4"); - - process_events(); - check_ptr(wd1_data.dir, ==, wd1); - check_str(wd1_data.name, ==, "test3"); - check_ptr(wd2_data.dir, ==, wd2); - check_str(wd2_data.name, ==, "test4"); - - reset_callback_data(&wd1_data, TR_WATCHDIR_ACCEPT); - reset_callback_data(&wd2_data, TR_WATCHDIR_ACCEPT); - create_file(dir1, "test5"); - create_dir(dir2, "test5"); - - process_events(); - check_ptr(wd1_data.dir, ==, wd1); - check_str(wd1_data.name, ==, "test5"); - check_ptr(wd2_data.dir, ==, NULL); - check_ptr(wd2_data.name, ==, NULL); - - reset_callback_data(&wd1_data, TR_WATCHDIR_ACCEPT); - reset_callback_data(&wd2_data, TR_WATCHDIR_ACCEPT); - create_dir(dir1, "test6"); - create_file(dir2, "test6"); - - process_events(); - check_ptr(wd1_data.dir, ==, NULL); - check_ptr(wd1_data.name, ==, NULL); - check_ptr(wd2_data.dir, ==, wd2); - check_str(wd2_data.name, ==, "test6"); - - reset_callback_data(&wd1_data, TR_WATCHDIR_ACCEPT); - reset_callback_data(&wd2_data, TR_WATCHDIR_ACCEPT); - create_dir(dir1, "test7"); - create_dir(dir2, "test7"); - - process_events(); - check_ptr(wd1_data.dir, ==, NULL); - check_ptr(wd1_data.name, ==, NULL); - check_ptr(wd2_data.dir, ==, NULL); - check_ptr(wd2_data.name, ==, NULL); - - tr_watchdir_free(wd2); - reset_callback_data(&wd2_data, TR_WATCHDIR_ACCEPT); - - tr_watchdir_free(wd1); - reset_callback_data(&wd1_data, TR_WATCHDIR_ACCEPT); - - event_base_free(ev_base); - - tr_free(dir2); - tr_free(dir1); - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -static int test_retry(void) -{ - char* const test_dir = libtest_sandbox_create(); - callback_data wd_data = CB_DATA_STATIC_INIT; - tr_watchdir_t wd; - - ev_base = event_base_new(); - - /* Speed up generic implementation */ - tr_watchdir_generic_interval = ONE_HUNDRED_MSEC; - - /* Tune retry logic */ - tr_watchdir_retry_limit = 10; - tr_watchdir_retry_start_interval = FIFTY_MSEC; - tr_watchdir_retry_max_interval = tr_watchdir_retry_start_interval; - - reset_callback_data(&wd_data, TR_WATCHDIR_RETRY); - wd = create_watchdir(test_dir, &callback, &wd_data, ev_base); - check_ptr(wd, !=, NULL); - - process_events(); - check_ptr(wd_data.dir, ==, NULL); - check_ptr(wd_data.name, ==, NULL); - - create_file(test_dir, "test"); - - process_events(); - check_ptr(wd_data.dir, ==, NULL); - check_ptr(wd_data.name, ==, NULL); - - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - - process_events(); - check_ptr(wd_data.dir, ==, wd); - check_str(wd_data.name, ==, "test"); - - tr_watchdir_free(wd); - reset_callback_data(&wd_data, TR_WATCHDIR_ACCEPT); - - event_base_free(ev_base); - - libtest_sandbox_destroy(test_dir); - tr_free(test_dir); - return 0; -} - -/*** -**** -***/ - -int main(void) -{ - testFunc const tests[] = - { - test_construct, - test_initial_scan, - test_watch, - test_watch_two_dirs, - test_retry - }; - - tr_net_init(); - - return runTests(tests, NUM_TESTS(tests)); -} diff --git a/libtransmission/watchdir-win32.c b/libtransmission/watchdir-win32.c index df2f77d15..4058937ef 100644 --- a/libtransmission/watchdir-win32.c +++ b/libtransmission/watchdir-win32.c @@ -18,7 +18,7 @@ #include #include -#define __LIBTRANSMISSION_WATCHDIR_MODULE__ +#define LIBTRANSMISSION_WATCHDIR_MODULE #include "transmission.h" #include "log.h" diff --git a/libtransmission/watchdir.c b/libtransmission/watchdir.c index d415b0cc2..8c0efc1d9 100644 --- a/libtransmission/watchdir.c +++ b/libtransmission/watchdir.c @@ -11,7 +11,7 @@ #include #include -#define __LIBTRANSMISSION_WATCHDIR_MODULE__ +#define LIBTRANSMISSION_WATCHDIR_MODULE #include "transmission.h" #include "error.h" diff --git a/libtransmission/watchdir.h b/libtransmission/watchdir.h index 7c0d2048d..3a5703c79 100644 --- a/libtransmission/watchdir.h +++ b/libtransmission/watchdir.h @@ -8,10 +8,9 @@ #pragma once -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS struct event_base; @@ -36,6 +35,4 @@ void tr_watchdir_free(tr_watchdir_t handle); char const* tr_watchdir_get_path(tr_watchdir_t handle); -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/libtransmission/web.c b/libtransmission/web.c index 4a1143c79..ef3dbbd48 100644 --- a/libtransmission/web.c +++ b/libtransmission/web.c @@ -394,7 +394,6 @@ static void tr_webThreadFunc(void* vsession) CURLM* multi; struct tr_web* web; int taskCount = 0; - struct tr_web_task* task; tr_session* session = vsession; uint32_t repeats = 0; @@ -457,7 +456,7 @@ static void tr_webThreadFunc(void* vsession) while (web->tasks != NULL) { /* pop the task */ - task = web->tasks; + struct tr_web_task* task = web->tasks; web->tasks = task->next; task->next = NULL; @@ -563,7 +562,7 @@ static void tr_webThreadFunc(void* vsession) * This is rare, but can happen on shutdown with unresponsive trackers. */ while (web->tasks != NULL) { - task = web->tasks; + struct tr_web_task* task = web->tasks; web->tasks = task->next; dbgmsg("Discarding task \"%s\"", task->url); task_free(task); diff --git a/libtransmission/web.h b/libtransmission/web.h index 98d252c71..39677fd06 100644 --- a/libtransmission/web.h +++ b/libtransmission/web.h @@ -10,10 +10,9 @@ #include -#ifdef __cplusplus -extern "C" -{ -#endif +#include "tr-macros.h" + +TR_BEGIN_DECLS struct tr_address; struct tr_web_task; @@ -58,6 +57,4 @@ void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest); char* tr_http_unescape(char const* str, size_t len); -#ifdef __cplusplus -} -#endif +TR_END_DECLS diff --git a/po/POTFILES.skip b/po/POTFILES.skip index b12e49b07..721e7f055 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -1 +1,9 @@ libtransmission/crypto-test.c +third-party/googletest/googlemock/include/gmock/gmock-matchers.h +third-party/googletest/googlemock/src/gmock-spec-builders.cc +third-party/googletest/googlemock/test/gmock-matchers_test.cc +third-party/googletest/googletest/include/gtest/internal/gtest-filepath.h +third-party/googletest/googletest/src/gtest.cc +third-party/googletest/googletest/test/googletest-catch-exceptions-test.py +third-party/googletest/googletest/test/googletest-filepath-test.cc +third-party/googletest/googletest/test/gtest_unittest.cc diff --git a/qt/.clang-tidy b/qt/.clang-tidy index 42b6992d2..a3edb4868 100644 --- a/qt/.clang-tidy +++ b/qt/.clang-tidy @@ -8,6 +8,19 @@ Checks: > cert-*, -cert-err58-cpp, clang-analyzer-optin*, + -clang-diagnostic-c++98*, + -clang-diagnostic-double-promotion, + -clang-diagnostic-exit-time-destructors, + -clang-diagnostic-global-constructors, + -clang-diagnostic-missing-prototypes, + -clang-diagnostic-nonportable-system-include-path, + -clang-diagnostic-old-style-cast, + -clang-diagnostic-shorten-64-to-32, + -clang-diagnostic-sign-compare, + -clang-diagnostic-sign-conversion, + -clang-diagnostic-switch-enum, + -clang-diagnostic-undefined-reinterpret-cast, ++ -clang-diagnostic-unused-member-function, cppcoreguidelines-*, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-init-variables, diff --git a/qt/AboutDialog.h b/qt/AboutDialog.h index 01797666d..0a3d9815e 100644 --- a/qt/AboutDialog.h +++ b/qt/AboutDialog.h @@ -11,7 +11,7 @@ #include #include "BaseDialog.h" - +#include "Macros.h" #include "ui_AboutDialog.h" class LicenseDialog; @@ -19,6 +19,7 @@ class LicenseDialog; class AboutDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(AboutDialog) public: AboutDialog(QWidget* parent = nullptr); diff --git a/qt/AddData.cc b/qt/AddData.cc index df61c7f09..ba154b3e2 100644 --- a/qt/AddData.cc +++ b/qt/AddData.cc @@ -6,11 +6,11 @@ * */ -#include #include +#include -#include #include // tr_base64_encode() +#include #include "AddData.h" #include "Utils.h" diff --git a/qt/Application.cc b/qt/Application.cc index feb57a331..1fee992c0 100644 --- a/qt/Application.cc +++ b/qt/Application.cc @@ -6,6 +6,9 @@ * */ +#include "Application.h" + +#include #include #include #include @@ -23,13 +26,12 @@ #include #endif -#include #include +#include #include #include #include "AddData.h" -#include "Application.h" #include "Formatter.h" #include "InteropHelper.h" #include "MainWindow.h" diff --git a/qt/Application.h b/qt/Application.h index cc0a2126b..a49c7423b 100644 --- a/qt/Application.h +++ b/qt/Application.h @@ -13,6 +13,7 @@ #include #include "FaviconCache.h" +#include "Macros.h" #include "Typedefs.h" class AddData; @@ -26,6 +27,7 @@ class WatchDir; class Application : public QApplication { Q_OBJECT + TR_DISABLE_COPY_MOVE(Application) public: Application(int& argc, char** argv); diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index f883d483f..1bab7eb54 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -51,6 +51,11 @@ set(${PROJECT_NAME}_SOURCES WatchDir.cc ) +string(REPLACE ";" " " CXX_WARNING_FLAGS_STR "${CXX_WARNING_FLAGS}") +foreach(FILE ${${PROJECT_NAME}_SOURCES}) + set_source_files_properties(${FILE} PROPERTIES COMPILE_FLAGS "${CXX_WARNING_FLAGS_STR}") +endforeach() + if (NOT ENABLE_QT_COM_INTEROP) set_source_files_properties(ComInteropHelper.cc PROPERTIES HEADER_FILE_ONLY ON) endif() @@ -184,6 +189,9 @@ endif() include_directories( ${CMAKE_SOURCE_DIR} ${PROJECT_SOURCE_DIR} +) +include_directories( + SYSTEM ${PROJECT_BINARY_DIR} ${CURL_INCLUDE_DIRS} ${EVENT2_INCLUDE_DIRS} diff --git a/qt/ColumnResizer.h b/qt/ColumnResizer.h index 7ee4720c4..bccdb0e52 100644 --- a/qt/ColumnResizer.h +++ b/qt/ColumnResizer.h @@ -11,12 +11,15 @@ #include #include +#include "Macros.h" + class QGridLayout; class QTimer; class ColumnResizer : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(ColumnResizer) public: ColumnResizer(QObject* parent = nullptr); diff --git a/qt/DetailsDialog.cc b/qt/DetailsDialog.cc index ea642efa2..c77d36832 100644 --- a/qt/DetailsDialog.cc +++ b/qt/DetailsDialog.cc @@ -508,8 +508,10 @@ void DetailsDialog::refreshUI() } } - double const d = size_when_done != 0 ? 100.0 * (size_when_done - left_until_done) / size_when_done : 100.0; - QString pct = Formatter::percentToString(d); + double const d = size_when_done == 0 ? + 100.0 : + 100.0 * static_cast(size_when_done - left_until_done) / static_cast(size_when_done); + auto const pct = Formatter::percentToString(d); if (have_unverified == 0 && left_until_done == 0) { @@ -548,7 +550,8 @@ void DetailsDialog::refreshUI() } else { - string = QStringLiteral("%1%").arg(Formatter::percentToString((100.0 * available) / size_when_done)); + auto const percent = 100.0 * static_cast(available) / static_cast(size_when_done); + string = QStringLiteral("%1%").arg(Formatter::percentToString(percent)); } ui_.availabilityValueLabel->setText(string); diff --git a/qt/DetailsDialog.h b/qt/DetailsDialog.h index bf1ec2d0b..e6ab41a9b 100644 --- a/qt/DetailsDialog.h +++ b/qt/DetailsDialog.h @@ -14,6 +14,7 @@ #include #include "BaseDialog.h" +#include "Macros.h" #include "Session.h" #include "Typedefs.h" @@ -32,6 +33,7 @@ class TrackerModelFilter; class DetailsDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(DetailsDialog) public: DetailsDialog(Session&, Prefs&, TorrentModel const&, QWidget* parent = nullptr); diff --git a/qt/FaviconCache.h b/qt/FaviconCache.h index ec7dcc0b1..c12f2679f 100644 --- a/qt/FaviconCache.h +++ b/qt/FaviconCache.h @@ -15,7 +15,8 @@ #include #include -#include // std::hash +#include "Macros.h" +#include "Utils.h" // std::hash class QNetworkAccessManager; class QNetworkReply; @@ -24,6 +25,7 @@ class QUrl; class FaviconCache : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(FaviconCache) public: FaviconCache(); diff --git a/qt/FileTreeDelegate.h b/qt/FileTreeDelegate.h index 1b121f17b..4dc1af4f6 100644 --- a/qt/FileTreeDelegate.h +++ b/qt/FileTreeDelegate.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class FileTreeDelegate : public QItemDelegate { Q_OBJECT + TR_DISABLE_COPY_MOVE(FileTreeDelegate) public: FileTreeDelegate(QObject* parent = nullptr) : diff --git a/qt/FileTreeItem.cc b/qt/FileTreeItem.cc index df2fbede6..727a072af 100644 --- a/qt/FileTreeItem.cc +++ b/qt/FileTreeItem.cc @@ -205,7 +205,7 @@ double FileTreeItem::progress() const if (total != 0) { - d = have / double(total); + d = static_cast(have) / static_cast(total); } return d; diff --git a/qt/FileTreeItem.h b/qt/FileTreeItem.h index 034c7dad4..73d41da1c 100644 --- a/qt/FileTreeItem.h +++ b/qt/FileTreeItem.h @@ -17,10 +17,12 @@ #include #include +#include "Macros.h" + class FileTreeItem { Q_DECLARE_TR_FUNCTIONS(FileTreeItem) - Q_DISABLE_COPY(FileTreeItem) + TR_DISABLE_COPY_MOVE(FileTreeItem) public: /* *INDENT-OFF* */ diff --git a/qt/FileTreeModel.h b/qt/FileTreeModel.h index 514b70b4d..14074756f 100644 --- a/qt/FileTreeModel.h +++ b/qt/FileTreeModel.h @@ -14,11 +14,14 @@ #include #include +#include "Macros.h" + class FileTreeItem; class FileTreeModel final : public QAbstractItemModel { Q_OBJECT + TR_DISABLE_COPY_MOVE(FileTreeModel) public: enum diff --git a/qt/FileTreeView.h b/qt/FileTreeView.h index 925851167..764fceb92 100644 --- a/qt/FileTreeView.h +++ b/qt/FileTreeView.h @@ -11,6 +11,7 @@ #include #include +#include "Macros.h" #include "Torrent.h" // FileList class QAction; @@ -23,6 +24,7 @@ class FileTreeModel; class FileTreeView : public QTreeView { Q_OBJECT + TR_DISABLE_COPY_MOVE(FileTreeView) public: FileTreeView(QWidget* parent = nullptr, bool editable = true); diff --git a/qt/FilterBar.cc b/qt/FilterBar.cc index b5d6b2deb..77ae4d4a0 100644 --- a/qt/FilterBar.cc +++ b/qt/FilterBar.cc @@ -6,6 +6,8 @@ * */ +#include "FilterBar.h" + #include // uint64_t #include #include @@ -17,10 +19,9 @@ #include "Application.h" #include "FaviconCache.h" -#include "Filters.h" -#include "FilterBar.h" #include "FilterBarComboBox.h" #include "FilterBarComboBoxDelegate.h" +#include "Filters.h" #include "Prefs.h" #include "Torrent.h" #include "TorrentFilter.h" diff --git a/qt/FilterBar.h b/qt/FilterBar.h index d76b68099..336190e20 100644 --- a/qt/FilterBar.h +++ b/qt/FilterBar.h @@ -15,6 +15,7 @@ #include #include "FaviconCache.h" +#include "Macros.h" #include "Torrent.h" #include "Typedefs.h" @@ -31,6 +32,7 @@ class TorrentModel; class FilterBar : public QWidget { Q_OBJECT + TR_DISABLE_COPY_MOVE(FilterBar) public: FilterBar(Prefs& prefs, TorrentModel const& torrents, TorrentFilter const& filter, QWidget* parent = nullptr); diff --git a/qt/FilterBarComboBox.h b/qt/FilterBarComboBox.h index c8ba5239d..109853d27 100644 --- a/qt/FilterBarComboBox.h +++ b/qt/FilterBarComboBox.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class FilterBarComboBox : public QComboBox { Q_OBJECT + TR_DISABLE_COPY_MOVE(FilterBarComboBox) public: enum diff --git a/qt/FilterBarComboBoxDelegate.h b/qt/FilterBarComboBoxDelegate.h index fdcc43227..fd9e06c08 100644 --- a/qt/FilterBarComboBoxDelegate.h +++ b/qt/FilterBarComboBoxDelegate.h @@ -10,12 +10,15 @@ #include +#include "Macros.h" + class QAbstractItemModel; class QComboBox; class FilterBarComboBoxDelegate : public QItemDelegate { Q_OBJECT + TR_DISABLE_COPY_MOVE(FilterBarComboBoxDelegate) public: FilterBarComboBoxDelegate(QObject* parent, QComboBox* combo); diff --git a/qt/Formatter.cc b/qt/Formatter.cc index c2e497aa7..cb8e797f1 100644 --- a/qt/Formatter.cc +++ b/qt/Formatter.cc @@ -6,14 +6,15 @@ * */ -#include - #include #include // tr_formatter #include "Formatter.h" #include "Speed.h" +#include +#include + /*** **** Constants ***/ diff --git a/qt/FreeSpaceLabel.h b/qt/FreeSpaceLabel.h index a91e177b1..ebf3454cc 100644 --- a/qt/FreeSpaceLabel.h +++ b/qt/FreeSpaceLabel.h @@ -12,6 +12,8 @@ #include #include +#include "Macros.h" + class Session; extern "C" @@ -22,6 +24,7 @@ struct tr_variant; class FreeSpaceLabel : public QLabel { Q_OBJECT + TR_DISABLE_COPY_MOVE(FreeSpaceLabel) public: FreeSpaceLabel(QWidget* parent = nullptr); diff --git a/qt/IconToolButton.h b/qt/IconToolButton.h index 8029d2aa5..70e985ca3 100644 --- a/qt/IconToolButton.h +++ b/qt/IconToolButton.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class IconToolButton : public QToolButton { Q_OBJECT + TR_DISABLE_COPY_MOVE(IconToolButton) public: IconToolButton(QWidget* parent = nullptr); diff --git a/qt/InteropObject.h b/qt/InteropObject.h index 682f12543..141fb8fd2 100644 --- a/qt/InteropObject.h +++ b/qt/InteropObject.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class InteropObject : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(InteropObject) #ifdef ENABLE_DBUS_INTEROP Q_CLASSINFO("D-Bus Interface", "com.transmissionbt.Transmission") diff --git a/qt/LicenseDialog.h b/qt/LicenseDialog.h index 9f7a7919b..b9c4434df 100644 --- a/qt/LicenseDialog.h +++ b/qt/LicenseDialog.h @@ -9,12 +9,13 @@ #pragma once #include "BaseDialog.h" - +#include "Macros.h" #include "ui_LicenseDialog.h" class LicenseDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(LicenseDialog) public: LicenseDialog(QWidget* parent = nullptr); diff --git a/qt/Macros.h b/qt/Macros.h new file mode 100644 index 000000000..70a98f3a2 --- /dev/null +++ b/qt/Macros.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) +# define TR_DISABLE_MOVE(Class) \ + Q_DISABLE_MOVE(Class) +# define TR_DISABLE_COPY_MOVE(Class) \ + Q_DISABLE_COPY_MOVE(Class) +#else +# define TR_DISABLE_MOVE(Class) \ + Class(Class &&) = delete; \ + Class& operator =(Class &&) = delete; +# define TR_DISABLE_COPY_MOVE(Class) \ + Q_DISABLE_COPY(Class) \ + TR_DISABLE_MOVE(Class) +#endif diff --git a/qt/MainWindow.cc b/qt/MainWindow.cc index a788d62b1..b156a0dfe 100644 --- a/qt/MainWindow.cc +++ b/qt/MainWindow.cc @@ -806,29 +806,33 @@ void MainWindow::refreshStatusBar(TransferStats const& stats) ui_.networkLabel->setVisible(!session_.isServer()); - QString const mode(prefs_.getString(Prefs::STATUSBAR_STATS)); - QString str; + auto const mode = prefs_.getString(Prefs::STATUSBAR_STATS); + auto str = QString {}; if (mode == SessionRatioStatsModeName) { - str = tr("Ratio: %1").arg(Formatter::ratioToString(session_.getStats().ratio)); + str = tr("Ratio: %1") + .arg(Formatter::ratioToString(session_.getStats().ratio)); } else if (mode == SessionTransferStatsModeName) { - tr_session_stats const& stats(session_.getStats()); - str = tr("Down: %1, Up: %2").arg(Formatter::sizeToString(stats.downloadedBytes)). - arg(Formatter::sizeToString(stats.uploadedBytes)); + auto const& st = session_.getStats(); + str = tr("Down: %1, Up: %2") + .arg(Formatter::sizeToString(st.downloadedBytes)) + .arg(Formatter::sizeToString(st.uploadedBytes)); } else if (mode == TotalTransferStatsModeName) { - tr_session_stats const& stats(session_.getCumulativeStats()); - str = tr("Down: %1, Up: %2").arg(Formatter::sizeToString(stats.downloadedBytes)). - arg(Formatter::sizeToString(stats.uploadedBytes)); + auto const& st = session_.getCumulativeStats(); + str = tr("Down: %1, Up: %2") + .arg(Formatter::sizeToString(st.downloadedBytes)) + .arg(Formatter::sizeToString(st.uploadedBytes)); } else // default is "total-ratio" { assert(mode == TotalRatioStatsModeName); - str = tr("Ratio: %1").arg(Formatter::ratioToString(session_.getCumulativeStats().ratio)); + str = tr("Ratio: %1") + .arg(Formatter::ratioToString(session_.getCumulativeStats().ratio)); } ui_.statsLabel->setText(str); diff --git a/qt/MainWindow.h b/qt/MainWindow.h index 92fba3845..f3d34a15f 100644 --- a/qt/MainWindow.h +++ b/qt/MainWindow.h @@ -19,10 +19,10 @@ #include #include "Filters.h" +#include "Macros.h" #include "Speed.h" #include "TorrentFilter.h" #include "Typedefs.h" - #include "ui_MainWindow.h" class QAction; @@ -51,6 +51,7 @@ struct tr_variant; class MainWindow : public QMainWindow { Q_OBJECT + TR_DISABLE_COPY_MOVE(MainWindow) public: MainWindow(Session&, Prefs&, TorrentModel&, bool minized); diff --git a/qt/MakeDialog.cc b/qt/MakeDialog.cc index 9fbd60a2c..13d400931 100644 --- a/qt/MakeDialog.cc +++ b/qt/MakeDialog.cc @@ -6,19 +6,20 @@ * */ +#include "MakeDialog.h" + #include #include #include #include #include -#include #include +#include #include #include "ColumnResizer.h" #include "Formatter.h" -#include "MakeDialog.h" #include "Session.h" #include "Utils.h" diff --git a/qt/MakeDialog.h b/qt/MakeDialog.h index 7cffec5d2..c9079146e 100644 --- a/qt/MakeDialog.h +++ b/qt/MakeDialog.h @@ -11,7 +11,7 @@ #include #include "BaseDialog.h" - +#include "Macros.h" #include "ui_MakeDialog.h" class QAbstractButton; @@ -26,6 +26,7 @@ struct tr_metainfo_builder; class MakeDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(MakeDialog) public: MakeDialog(Session&, QWidget* parent = nullptr); diff --git a/qt/OptionsDialog.h b/qt/OptionsDialog.h index 1ee645fb1..0d8af5ee2 100644 --- a/qt/OptionsDialog.h +++ b/qt/OptionsDialog.h @@ -18,8 +18,8 @@ #include "AddData.h" // AddData #include "BaseDialog.h" +#include "Macros.h" #include "Torrent.h" // FileList - #include "ui_OptionsDialog.h" class Prefs; @@ -33,6 +33,7 @@ struct tr_variant; class OptionsDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(OptionsDialog) public: OptionsDialog(Session& session, Prefs const& prefs, AddData addme, QWidget* parent = nullptr); diff --git a/qt/PathButton.h b/qt/PathButton.h index c4f6b8b67..42b80ab7f 100644 --- a/qt/PathButton.h +++ b/qt/PathButton.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class PathButton : public QToolButton { Q_OBJECT + TR_DISABLE_COPY_MOVE(PathButton) public: enum Mode diff --git a/qt/Prefs.h b/qt/Prefs.h index 24040ac8c..7bf07c5e6 100644 --- a/qt/Prefs.h +++ b/qt/Prefs.h @@ -18,6 +18,7 @@ #include #include "Filters.h" +#include "Macros.h" class QDateTime; @@ -29,6 +30,7 @@ struct tr_variant; class Prefs : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(Prefs) public: enum diff --git a/qt/PrefsDialog.cc b/qt/PrefsDialog.cc index 4ab39a7a1..9b80db0d2 100644 --- a/qt/PrefsDialog.cc +++ b/qt/PrefsDialog.cc @@ -6,6 +6,8 @@ * */ +#include "PrefsDialog.h" + #ifdef _WIN32 #include // FD_SETSIZE #else @@ -36,10 +38,9 @@ #include #include "ColumnResizer.h" -#include "FreeSpaceLabel.h" #include "Formatter.h" +#include "FreeSpaceLabel.h" #include "Prefs.h" -#include "PrefsDialog.h" #include "Session.h" #include "Utils.h" diff --git a/qt/PrefsDialog.h b/qt/PrefsDialog.h index ff980f7eb..512cffc3f 100644 --- a/qt/PrefsDialog.h +++ b/qt/PrefsDialog.h @@ -11,8 +11,8 @@ #include #include "BaseDialog.h" +#include "Macros.h" #include "Prefs.h" - #include "ui_PrefsDialog.h" class QHttp; @@ -25,6 +25,7 @@ class Session; class PrefsDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(PrefsDialog) public: PrefsDialog(Session&, Prefs&, QWidget* parent = nullptr); diff --git a/qt/RelocateDialog.h b/qt/RelocateDialog.h index 0bd7f1f73..1cd08f67e 100644 --- a/qt/RelocateDialog.h +++ b/qt/RelocateDialog.h @@ -9,8 +9,8 @@ #pragma once #include "BaseDialog.h" +#include "Macros.h" #include "Typedefs.h" - #include "ui_RelocateDialog.h" class Session; @@ -19,6 +19,7 @@ class TorrentModel; class RelocateDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(RelocateDialog) public: RelocateDialog(Session&, TorrentModel const&, torrent_ids_t ids, QWidget* parent = nullptr); diff --git a/qt/RpcClient.cc b/qt/RpcClient.cc index 8220cc404..7bf0b09c4 100644 --- a/qt/RpcClient.cc +++ b/qt/RpcClient.cc @@ -6,6 +6,8 @@ * */ +#include "RpcClient.h" + #include #include @@ -15,12 +17,11 @@ #include #include -#include #include +#include #include // tr_free #include // LONG_VERSION_STRING -#include "RpcClient.h" #include "VariantHelpers.h" // #define DEBUG_HTTP @@ -210,7 +211,7 @@ QNetworkAccessManager* RpcClient::networkAccessManager() return nam_; } -void RpcClient::localSessionCallback(tr_session* s, tr_variant* response, void* vself) +void RpcClient::localSessionCallback(tr_session* s, tr_variant* response, void* vself) noexcept { Q_UNUSED(s) diff --git a/qt/RpcClient.h b/qt/RpcClient.h index d0917dc0a..57ce98ca4 100644 --- a/qt/RpcClient.h +++ b/qt/RpcClient.h @@ -25,6 +25,8 @@ #include #include +#include "Macros.h" + class QByteArray; class QNetworkAccessManager; @@ -53,6 +55,7 @@ using RpcResponseFuture = QFuture; class RpcClient : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(RpcClient) public: RpcClient(QObject* parent = nullptr); @@ -87,7 +90,7 @@ private: int64_t parseResponseTag(tr_variant& response); RpcResponse parseResponseData(tr_variant& response); - static void localSessionCallback(tr_session* s, tr_variant* response, void* vself); + static void localSessionCallback(tr_session* s, tr_variant* response, void* vself) noexcept; std::optional request_; diff --git a/qt/RpcQueue.h b/qt/RpcQueue.h index b46b9ab9f..f7d8331d5 100644 --- a/qt/RpcQueue.h +++ b/qt/RpcQueue.h @@ -18,11 +18,13 @@ #include #include +#include "Macros.h" #include "RpcClient.h" class RpcQueue : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(RpcQueue) public: explicit RpcQueue(QObject* parent = nullptr); diff --git a/qt/Session.cc b/qt/Session.cc index 126abf2e9..0b80ba628 100644 --- a/qt/Session.cc +++ b/qt/Session.cc @@ -6,6 +6,8 @@ * */ +#include "Session.h" + #include #include @@ -21,8 +23,8 @@ #include #include -#include #include +#include #include // tr_free #include @@ -30,7 +32,6 @@ #include "CustomVariantType.h" #include "Prefs.h" #include "RpcQueue.h" -#include "Session.h" #include "SessionDialog.h" #include "Torrent.h" #include "Utils.h" diff --git a/qt/Session.h b/qt/Session.h index 4b660c5fb..dd6f2d9b4 100644 --- a/qt/Session.h +++ b/qt/Session.h @@ -17,6 +17,7 @@ #include #include +#include "Macros.h" #include "RpcClient.h" #include "RpcQueue.h" #include "Torrent.h" @@ -33,6 +34,7 @@ struct tr_variant; class Session : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(Session) public: Session(QString config_dir, Prefs& prefs); diff --git a/qt/SessionDialog.h b/qt/SessionDialog.h index 5193afc3a..7a3aa1a75 100644 --- a/qt/SessionDialog.h +++ b/qt/SessionDialog.h @@ -11,7 +11,7 @@ #include #include "BaseDialog.h" - +#include "Macros.h" #include "ui_SessionDialog.h" class Prefs; @@ -20,6 +20,7 @@ class Session; class SessionDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(SessionDialog) public: SessionDialog(Session& session, Prefs& prefs, QWidget* parent = nullptr); diff --git a/qt/SqueezeLabel.h b/qt/SqueezeLabel.h index 474ead1b6..ba6fed41c 100644 --- a/qt/SqueezeLabel.h +++ b/qt/SqueezeLabel.h @@ -43,9 +43,12 @@ #include +#include "Macros.h" + class SqueezeLabel : public QLabel { Q_OBJECT + TR_DISABLE_COPY_MOVE(SqueezeLabel) public: SqueezeLabel(QWidget* parent = nullptr); diff --git a/qt/StatsDialog.h b/qt/StatsDialog.h index 6d41074cb..4e1487522 100644 --- a/qt/StatsDialog.h +++ b/qt/StatsDialog.h @@ -9,7 +9,7 @@ #pragma once #include "BaseDialog.h" - +#include "Macros.h" #include "ui_StatsDialog.h" class QTimer; @@ -19,6 +19,7 @@ class Session; class StatsDialog : public BaseDialog { Q_OBJECT + TR_DISABLE_COPY_MOVE(StatsDialog) public: StatsDialog(Session&, QWidget* parent = nullptr); diff --git a/qt/Torrent.cc b/qt/Torrent.cc index 7b28b6490..a0f34b698 100644 --- a/qt/Torrent.cc +++ b/qt/Torrent.cc @@ -371,11 +371,11 @@ Torrent::fields_t Torrent::update(tr_quark const* keys, tr_variant const* const* case TR_KEY_trackers: { - FaviconCache::Keys keys; + FaviconCache::Keys tmp; std::transform(std::cbegin(tracker_stats_), std::cend(tracker_stats_), - std::inserter(keys, std::end(keys)), + std::inserter(tmp, std::end(tmp)), [](auto const& ts) { return ts.favicon_key; }); - std::swap(tracker_keys_, keys); + std::swap(tracker_keys_, tmp); break; } } diff --git a/qt/Torrent.h b/qt/Torrent.h index d1a070ad0..21442a3ea 100644 --- a/qt/Torrent.h +++ b/qt/Torrent.h @@ -20,6 +20,7 @@ #include #include "FaviconCache.h" +#include "Macros.h" #include "Speed.h" #ifdef ERROR @@ -105,6 +106,7 @@ using FileList = QVector; class Torrent : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(Torrent) public: Torrent(Prefs const&, int id); diff --git a/qt/TorrentDelegate.cc b/qt/TorrentDelegate.cc index 50ef927de..73cfe58d2 100644 --- a/qt/TorrentDelegate.cc +++ b/qt/TorrentDelegate.cc @@ -362,10 +362,6 @@ QString TorrentDelegate::statusString(Torrent const& tor) } break; - - default: - str = tr("Error"); - break; } } @@ -445,8 +441,9 @@ void TorrentDelegate::setProgressBarPercentDone(QStyleOptionViewItem const& opti if (tor.isSeeding() && tor.getSeedRatio(seed_ratio_limit)) { - double const seed_rate_ratio = tor.ratio() / seed_ratio_limit; - int const scaled_progress = seed_rate_ratio * (progress_bar_style_.maximum - progress_bar_style_.minimum); + auto const seed_rate_ratio = tor.ratio() / seed_ratio_limit; + auto const scaled_progress = + static_cast(seed_rate_ratio * (progress_bar_style_.maximum - progress_bar_style_.minimum)); progress_bar_style_.progress = progress_bar_style_.minimum + scaled_progress; } else diff --git a/qt/TorrentDelegate.h b/qt/TorrentDelegate.h index 9be170937..f633e5506 100644 --- a/qt/TorrentDelegate.h +++ b/qt/TorrentDelegate.h @@ -12,6 +12,8 @@ #include +#include "Macros.h" + class QStyle; class QStyleOptionProgressBar; @@ -20,6 +22,7 @@ class Torrent; class TorrentDelegate : public QStyledItemDelegate { Q_OBJECT + TR_DISABLE_COPY_MOVE(TorrentDelegate) public: explicit TorrentDelegate(QObject* parent = nullptr); diff --git a/qt/TorrentDelegateMin.h b/qt/TorrentDelegateMin.h index 9bf0d49b6..e6082f260 100644 --- a/qt/TorrentDelegateMin.h +++ b/qt/TorrentDelegateMin.h @@ -8,11 +8,13 @@ #pragma once +#include "Macros.h" #include "TorrentDelegate.h" class TorrentDelegateMin : public TorrentDelegate { Q_OBJECT + TR_DISABLE_COPY_MOVE(TorrentDelegateMin) public: explicit TorrentDelegateMin(QObject* parent = nullptr) : diff --git a/qt/TorrentFilter.h b/qt/TorrentFilter.h index bf7717a4c..e05ee233a 100644 --- a/qt/TorrentFilter.h +++ b/qt/TorrentFilter.h @@ -14,6 +14,7 @@ #include #include "Filters.h" +#include "Macros.h" class QString; @@ -24,6 +25,7 @@ class Torrent; class TorrentFilter : public QSortFilterProxyModel { Q_OBJECT + TR_DISABLE_COPY_MOVE(TorrentFilter) public: enum TextMode diff --git a/qt/TorrentModel.h b/qt/TorrentModel.h index 26e50edf3..6472458db 100644 --- a/qt/TorrentModel.h +++ b/qt/TorrentModel.h @@ -14,6 +14,7 @@ #include #include +#include "Macros.h" #include "Torrent.h" #include "Typedefs.h" @@ -28,6 +29,7 @@ struct tr_variant; class TorrentModel : public QAbstractListModel { Q_OBJECT + TR_DISABLE_COPY_MOVE(TorrentModel) public: enum Role diff --git a/qt/TorrentView.h b/qt/TorrentView.h index fca8d4bf3..68666892b 100644 --- a/qt/TorrentView.h +++ b/qt/TorrentView.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class TorrentView : public QListView { Q_OBJECT + TR_DISABLE_COPY_MOVE(TorrentView) public: TorrentView(QWidget* parent = nullptr); diff --git a/qt/TrackerDelegate.h b/qt/TrackerDelegate.h index 7594aeb52..63d5ad727 100644 --- a/qt/TrackerDelegate.h +++ b/qt/TrackerDelegate.h @@ -10,6 +10,8 @@ #include +#include "Macros.h" + class QStyle; class Session; @@ -18,6 +20,7 @@ struct TrackerInfo; class TrackerDelegate : public QItemDelegate { Q_OBJECT + TR_DISABLE_COPY_MOVE(TrackerDelegate) public: TrackerDelegate(QObject* parent = nullptr) : diff --git a/qt/TrackerModel.h b/qt/TrackerModel.h index 891bddea7..e97b0dcd7 100644 --- a/qt/TrackerModel.h +++ b/qt/TrackerModel.h @@ -11,6 +11,7 @@ #include #include +#include "Macros.h" #include "Torrent.h" #include "Typedefs.h" @@ -27,6 +28,7 @@ Q_DECLARE_METATYPE(TrackerInfo) class TrackerModel : public QAbstractListModel { Q_OBJECT + TR_DISABLE_COPY_MOVE(TrackerModel) public: enum Role diff --git a/qt/TrackerModelFilter.h b/qt/TrackerModelFilter.h index 4b566cf0e..905c67675 100644 --- a/qt/TrackerModelFilter.h +++ b/qt/TrackerModelFilter.h @@ -10,9 +10,12 @@ #include +#include "Macros.h" + class TrackerModelFilter : public QSortFilterProxyModel { Q_OBJECT + TR_DISABLE_COPY_MOVE(TrackerModelFilter) public: TrackerModelFilter(QObject* parent = nullptr); @@ -26,7 +29,7 @@ public: protected: // QSortFilterProxyModel - virtual bool filterAcceptsRow(int source_row, QModelIndex const& source_parent) const; + virtual bool filterAcceptsRow(int source_row, QModelIndex const& source_parent) const override; private: bool show_backups_ = {}; diff --git a/qt/Utils.cc b/qt/Utils.cc index e08aeba42..6d6a8ed68 100644 --- a/qt/Utils.cc +++ b/qt/Utils.cc @@ -47,6 +47,33 @@ namespace { +bool isSlashChar(QChar const& c) +{ + return c == QLatin1Char('/') || c == QLatin1Char('\\'); +} + +QIcon folderIcon() +{ + static QIcon icon; + if (icon.isNull()) + { + icon = QFileIconProvider().icon(QFileIconProvider::Folder); + } + + return icon; +} + +QIcon fileIcon() +{ + static QIcon icon; + if (icon.isNull()) + { + icon = QFileIconProvider().icon(QFileIconProvider::File); + } + + return icon; +} + #ifdef _WIN32 void addAssociatedFileIcon(QFileInfo const& file_info, UINT icon_size, QIcon& icon) @@ -81,34 +108,7 @@ void addAssociatedFileIcon(QFileInfo const& file_info, UINT icon_size, QIcon& ic } } -#endif - -bool isSlashChar(QChar const& c) -{ - return c == QLatin1Char('/') || c == QLatin1Char('\\'); -} - -QIcon folderIcon() -{ - static QIcon icon; - if (icon.isNull()) - { - icon = QFileIconProvider().icon(QFileIconProvider::Folder); - } - - return icon; -} - -QIcon fileIcon() -{ - static QIcon icon; - if (icon.isNull()) - { - icon = QFileIconProvider().icon(QFileIconProvider::File); - } - - return icon; -} +#else std::unordered_map icon_cache; @@ -156,6 +156,8 @@ QIcon getMimeIcon(QString const& filename) return icon; } +#endif + } // namespace QIcon Utils::getFolderIcon() diff --git a/qt/VariantHelpers.cc b/qt/VariantHelpers.cc index 00e7494c5..3151aa628 100644 --- a/qt/VariantHelpers.cc +++ b/qt/VariantHelpers.cc @@ -8,8 +8,8 @@ #include "VariantHelpers.h" -#include // qFuzzyCompare #include +#include // qFuzzyCompare #include "Application.h" // qApp #include "Filters.h" @@ -128,11 +128,11 @@ bool change(TrackerStat& setme, tr_variant const* value) HANDLE_KEY(downloadCount, download_count) HANDLE_KEY(hasAnnounced, has_announced) HANDLE_KEY(hasScraped, has_scraped) - HANDLE_KEY(host, host); - HANDLE_KEY(id, id); - HANDLE_KEY(isBackup, is_backup); - HANDLE_KEY(lastAnnouncePeerCount, last_announce_peer_count); - HANDLE_KEY(lastAnnounceResult, last_announce_result); + HANDLE_KEY(host, host) + HANDLE_KEY(id, id) + HANDLE_KEY(isBackup, is_backup) + HANDLE_KEY(lastAnnouncePeerCount, last_announce_peer_count) + HANDLE_KEY(lastAnnounceResult, last_announce_result) HANDLE_KEY(lastAnnounceStartTime, last_announce_start_time) HANDLE_KEY(lastAnnounceSucceeded, last_announce_succeeded) HANDLE_KEY(lastAnnounceTime, last_announce_time) diff --git a/qt/WatchDir.h b/qt/WatchDir.h index 7542922df..9178556da 100644 --- a/qt/WatchDir.h +++ b/qt/WatchDir.h @@ -15,11 +15,14 @@ #include #include +#include "Macros.h" + class TorrentModel; class WatchDir : public QObject { Q_OBJECT + TR_DISABLE_COPY_MOVE(WatchDir) public: WatchDir(TorrentModel const&); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..740818ba1 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +#set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +include(GoogleTest) + +include_directories( + SYSTEM + ${CMAKE_SOURCE_DIR}/third-party/googletest/googletest/include + ${CMAKE_SOURCE_DIR}/third-party/googletest/googletest +) + +add_subdirectory(gtest) +add_subdirectory(libtransmission) diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 000000000..83e55b57d --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,7 @@ +SUBDIRS = \ + gtest \ + libtransmission + +EXTRA_DIST = \ + CMakeLists.txt + diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt new file mode 100644 index 000000000..1d617d2b8 --- /dev/null +++ b/tests/gtest/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library( + gtestall + STATIC + ${CMAKE_SOURCE_DIR}/third-party/googletest/googletest/src/gtest-all.cc + ${CMAKE_SOURCE_DIR}/third-party/googletest/googletest/src/gtest_main.cc +) diff --git a/tests/gtest/Makefile.am b/tests/gtest/Makefile.am new file mode 100644 index 000000000..9d2bcabde --- /dev/null +++ b/tests/gtest/Makefile.am @@ -0,0 +1,30 @@ +check_LTLIBRARIES = \ + libgtest.la \ + libgtest_main.la + +GTEST_DIR = $(top_srcdir)/third-party/googletest + +GOOGLE_TEST_INCLUDES = \ + -I$(top_srcdir)/third-party/googletest/googletest/include \ + -I$(top_srcdir)/third-party/googletest/googletest + +libgtest_la_SOURCES = \ + $(GTEST_DIR)/googletest/src/gtest-all.cc + +libgtest_la_LDFLAGS = \ + -pthread + +libgtest_la_CPPFLAGS = \ + $(GOOGLE_TEST_INCLUDES) + +libgtest_main_la_SOURCES = \ + $(GTEST_DIR)/googletest/src/gtest_main.cc + +libgtest_main_la_CPPFLAGS = \ + $(GOOGLE_TEST_INCLUDES) + +libgtest_main_la_LIBADD = \ + $(top_builddir)/tests/gtest/libgtest.la + +EXTRA_DIST = \ + CMakeLists.txt diff --git a/tests/libtransmission/.clang-tidy b/tests/libtransmission/.clang-tidy new file mode 100644 index 000000000..2b8b6aafa --- /dev/null +++ b/tests/libtransmission/.clang-tidy @@ -0,0 +1,78 @@ +--- +# Many of these checks are disabled only because the code hasn't been +# cleaned up yet. Pull requests welcomed. +Checks: > + bugprone-*, + -bugprone-branch-clone, + -bugprone-narrowing-conversions, + cert-*, + -cert-dcl50-cpp, + -cert-err58-cpp, + clang-analyzer-optin*, + -clang-diagnostic-c++98*, + -clang-diagnostic-global-constructors, + -clang-diagnostic-missing-prototypes, + -clang-diagnostic-nonportable-system-include-path, + -clang-diagnostic-old-style-cast, + -clang-diagnostic-shorten-64-to-32, + -clang-diagnostic-sign-compare, + -clang-diagnostic-sign-conversion, + cppcoreguidelines-*, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-init-variables, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-narrowing-conversions, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-const-cast, + -cppcoreguidelines-pro-type-cstyle-cast, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-static-cast-downcast, + -cppcoreguidelines-pro-type-vararg, + -cppcoreguidelines-special-member-functions, + google-readability-*, + google-runtime-operator, + hicpp-*, + -hicpp-multiway-paths-covered, + -hicpp-no-array-decay, + -hicpp-signed-bitwise, + -hicpp-special-member-functions, + -hicpp-vararg, + misc-*, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + modernize-*, + -modernize-raw-string-literal, + -modernize-concat-nested-namespaces, + -modernize-use-trailing-return-type, + performance-*, + readability-*, + -readability-convert-member-functions-to-static, + -readability-implicit-bool-conversion, + -readability-inconsistent-declaration-parameter-name, + -readability-magic-numbers, + -readability-qualified-auto, + -readability-redundant-access-specifiers, + -readability-static-accessed-through-instance + +WarningsAsErrors: > + * + +CheckOptions: + - { key: readability-identifier-naming.ClassCase, value: CamelCase } + - { key: readability-identifier-naming.ClassMethodCase, value: camelBack } + - { key: readability-identifier-naming.ConstexprVariableCase, value: CamelCase } + - { key: readability-identifier-naming.EnumConstantCase, value: UPPER_CASE } + - { key: readability-identifier-naming.FunctionCase, value: camelBack } + - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } + - { key: readability-identifier-naming.MemberConstantCase, value: CamelCase } + - { key: readability-identifier-naming.NamespaceCase, value: lower_case } + - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } + - { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ } + - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } + - { key: readability-identifier-naming.StructCase, value: CamelCase } + - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } + - { key: readability-identifier-naming.VariableCase, value: lower_case } diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt new file mode 100644 index 000000000..b5c918e65 --- /dev/null +++ b/tests/libtransmission/CMakeLists.txt @@ -0,0 +1,47 @@ +set(crypto-test_ADD_SOURCES crypto-test-ref.h) +set(subprocess-test_ADD_SOURCES subprocess-test.cmd) + +add_compile_options( + ${CXX_WARNING_FLAGS} +) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + # patches welcomed + add_compile_options(-Wno-sign-compare) +endif() + +add_definitions( + -D__TRANSMISSION__ +) + +include_directories( + ${CMAKE_SOURCE_DIR}/libtransmission + ${CMAKE_SOURCE_DIR}/tests/libtransmission + ${CMAKE_BINARY_DIR}/libtransmission +) +include_directories( + SYSTEM + ${CURL_INCLUDE_DIRS} + ${EVENT2_INCLUDE_DIRS} +) + +set_property(DIRECTORY PROPERTY FOLDER "UnitTests") +set_property(DIRECTORY PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") + +foreach(T bitfield blocklist clients crypto error file getopt + history json magnet makemeta metainfo move peer-msgs + quark rename rpc session subprocess utils variant watchdir) + set(TP libtransmission-test-${T}) + add_executable(${TP} ${T}-test.cc) + target_link_libraries(${TP} gtestall ${TR_NAME}) + add_test(NAME ${TP} COMMAND ${TP}) +endforeach() + +add_custom_command( + TARGET libtransmission-test-subprocess + PRE_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${PROJECT_SOURCE_DIR}/tests/libtransmission/subprocess-test.cmd + $/libtransmission-test-subprocess.cmd +) diff --git a/tests/libtransmission/Makefile.am b/tests/libtransmission/Makefile.am new file mode 100644 index 000000000..d177c43f5 --- /dev/null +++ b/tests/libtransmission/Makefile.am @@ -0,0 +1,93 @@ +#check_PROGRAMS = gtest +# +#gtest_SOURCES = gtest.cpp +#gtest_LDADD = libgtest.la +#gtest_LDFLAGS = -pthread +# +#gtest_CPPFLAGS = \ +# -I$(top_srcdir)/googletest/googletest/include \ +# -I$(top_srcdir)/googletest/googletest \ +# -pthread + +AM_CPPFLAGS = \ + -std=gnu++17 \ + -D__TRANSMISSION__ \ + @LIBEVENT_CFLAGS@ \ + @LIBCURL_CFLAGS@ \ + -I$(top_srcdir)/third-party/googletest/googletest/include \ + -I$(top_srcdir)/libtransmission \ + -I$(srcdir) \ + -pthread + +LDADD = \ + $(top_builddir)/tests/gtest/libgtest_main.la \ + $(top_builddir)/tests/gtest/libgtest.la \ + $(top_builddir)/libtransmission/libtransmission.a \ + @LIBUPNP_LIBS@ \ + @LIBNATPMP_LIBS@ \ + @DHT_LIBS@ \ + @LIBB64_LIBS@ \ + @LIBUTP_LIBS@ \ + @GTK_LIBS@ \ + @LIBAPPINDICATOR_LIBS@ \ + @LIBEVENT_LIBS@ \ + @LIBCURL_LIBS@ \ + @CRYPTO_LIBS@ \ + @ZLIB_LIBS@ \ + @PTHREAD_LIBS@ \ + ${LIBM} + +TESTS = \ + test_bitfield \ + test_blocklist \ + test_clients \ + test_crypto \ + test_error \ + test_file \ + test_getopt \ + test_history \ + test_json \ + test_magnet \ + test_makemeta \ + test_metainfo \ + test_move \ + test_peer_msgs \ + test_quark \ + test_rename \ + test_rpc \ + test_session \ + test_subprocess \ + test_utils \ + test_variant \ + test_watchdir + +check_PROGRAMS = $(TESTS) + +test_bitfield_SOURCES = bitfield-test.cc +test_blocklist_SOURCES = blocklist-test.cc +test_clients_SOURCES = clients-test.cc +test_crypto_SOURCES = crypto-test.cc +test_error_SOURCES = error-test.cc +test_file_SOURCES = file-test.cc +test_getopt_SOURCES = getopt-test.cc +test_history_SOURCES = history-test.cc +test_json_SOURCES = json-test.cc +test_magnet_SOURCES = magnet-test.cc +test_makemeta_SOURCES = makemeta-test.cc +test_metainfo_SOURCES = metainfo-test.cc +test_move_SOURCES = move-test.cc +test_peer_msgs_SOURCES = peer-msgs-test.cc +test_quark_SOURCES = quark-test.cc +test_rename_SOURCES = rename-test.cc +test_rpc_SOURCES = rpc-test.cc +test_session_SOURCES = session-test.cc +test_subprocess_SOURCES = subprocess-test.cc +test_utils_SOURCES = utils-test.cc +test_variant_SOURCES = variant-test.cc +test_watchdir_SOURCES = watchdir-test.cc + +EXTRA_DIST = \ + CMakeLists.txt \ + subprocess-test.cmd \ + crypto-test-ref.h \ + test-fixtures.h diff --git a/tests/libtransmission/bitfield-test.cc b/tests/libtransmission/bitfield-test.cc new file mode 100644 index 000000000..932f78c8f --- /dev/null +++ b/tests/libtransmission/bitfield-test.cc @@ -0,0 +1,218 @@ +/* + * This file Copyright (C) 2010-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "crypto-utils.h" +#include "bitfield.h" +#include "utils.h" /* tr_free */ + +#include "gtest/gtest.h" + +TEST(Bitfield, countRange) +{ + auto constexpr IterCount = int{ 10000 }; + + for (auto i = 0; i < IterCount; ++i) + { + int const bit_count = 100 + tr_rand_int_weak(1000); + + // generate a random bitfield + tr_bitfield bf; + tr_bitfieldConstruct(&bf, bit_count); + + for (int j = 0, n = tr_rand_int_weak(bit_count); j < n; ++j) + { + tr_bitfieldAdd(&bf, tr_rand_int_weak(bit_count)); + } + + int begin = tr_rand_int_weak(bit_count); + int end; + do + { + end = tr_rand_int_weak(bit_count); + } + while (end == begin); + + // ensure end <= begin + if (end < begin) + { + int const tmp = begin; + begin = end; + end = tmp; + } + + // test the bitfield + unsigned long count1 = {}; + for (auto j = begin; j < end; ++j) + { + if (tr_bitfieldHas(&bf, j)) + { + ++count1; + } + } + + auto const count2 = tr_bitfieldCountRange(&bf, begin, end); + EXPECT_EQ(count1, count2); + + // cleanup + tr_bitfieldDestruct(&bf); + } +} + +TEST(Bitfields, bitfields) +{ + unsigned int bitcount = 500; + tr_bitfield field; + + tr_bitfieldConstruct(&field, bitcount); + + /* test tr_bitfieldAdd */ + for (unsigned int i = 0; i < bitcount; i++) + { + if (i % 7 == 0) + { + tr_bitfieldAdd(&field, i); + } + } + + for (unsigned int i = 0; i < bitcount; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (i % 7 == 0)); + } + + /* test tr_bitfieldAddRange */ + tr_bitfieldAddRange(&field, 0, bitcount); + + for (unsigned int i = 0; i < bitcount; i++) + { + EXPECT_TRUE(tr_bitfieldHas(&field, i)); + } + + /* test tr_bitfieldRem */ + for (unsigned int i = 0; i < bitcount; i++) + { + if (i % 7 != 0) + { + tr_bitfieldRem(&field, i); + } + } + + for (unsigned int i = 0; i < bitcount; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (i % 7 == 0)); + } + + /* test tr_bitfieldRemRange in the middle of a boundary */ + tr_bitfieldAddRange(&field, 0, 64); + tr_bitfieldRemRange(&field, 4, 21); + + for (unsigned int i = 0; i < 64; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (i < 4 || i >= 21)); + } + + /* test tr_bitfieldRemRange on the boundaries */ + tr_bitfieldAddRange(&field, 0, 64); + tr_bitfieldRemRange(&field, 8, 24); + + for (unsigned int i = 0; i < 64; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (i < 8 || i >= 24)); + } + + /* test tr_bitfieldRemRange when begin & end is on the same word */ + tr_bitfieldAddRange(&field, 0, 64); + tr_bitfieldRemRange(&field, 4, 5); + + for (unsigned int i = 0; i < 64; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (i < 4 || i >= 5)); + } + + /* test tr_bitfieldAddRange */ + tr_bitfieldRemRange(&field, 0, 64); + tr_bitfieldAddRange(&field, 4, 21); + + for (unsigned int i = 0; i < 64; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (4 <= i && i < 21)); + } + + /* test tr_bitfieldAddRange on the boundaries */ + tr_bitfieldRemRange(&field, 0, 64); + tr_bitfieldAddRange(&field, 8, 24); + + for (unsigned int i = 0; i < 64; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (8 <= i && i < 24)); + } + + /* test tr_bitfieldAddRange when begin & end is on the same word */ + tr_bitfieldRemRange(&field, 0, 64); + tr_bitfieldAddRange(&field, 4, 5); + + for (unsigned int i = 0; i < 64; i++) + { + EXPECT_EQ(tr_bitfieldHas(&field, i), (4 <= i && i < 5)); + } + + tr_bitfieldDestruct(&field); +} + +TEST(Bitfields, hasAllNone) +{ + tr_bitfield field; + + tr_bitfieldConstruct(&field, 3); + + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(tr_bitfieldHasNone(&field)); + + tr_bitfieldAdd(&field, 0); + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldRem(&field, 0); + tr_bitfieldAdd(&field, 1); + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldRem(&field, 1); + tr_bitfieldAdd(&field, 2); + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldAdd(&field, 0); + tr_bitfieldAdd(&field, 1); + EXPECT_TRUE(tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldSetHasNone(&field); + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(tr_bitfieldHasNone(&field)); + + tr_bitfieldSetHasAll(&field); + EXPECT_TRUE(tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldDestruct(&field); + tr_bitfieldConstruct(&field, 0); + + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldSetHasNone(&field); + EXPECT_TRUE(!tr_bitfieldHasAll(&field)); + EXPECT_TRUE(tr_bitfieldHasNone(&field)); + + tr_bitfieldSetHasAll(&field); + EXPECT_TRUE(tr_bitfieldHasAll(&field)); + EXPECT_TRUE(!tr_bitfieldHasNone(&field)); + + tr_bitfieldDestruct(&field); +} diff --git a/tests/libtransmission/blocklist-test.cc b/tests/libtransmission/blocklist-test.cc new file mode 100644 index 000000000..b9cb1c6fe --- /dev/null +++ b/tests/libtransmission/blocklist-test.cc @@ -0,0 +1,146 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include +#include // strlen() +// #include // sync() + +#include "transmission.h" +#include "blocklist.h" +#include "file.h" +#include "peer-socket.h" +#include "net.h" +#include "session.h" // tr_sessionIsAddressBlocked() +#include "utils.h" + +#include "test-fixtures.h" + +namespace libtransmission +{ + +namespace test +{ + +class BlocklistTest : public SessionTest +{ +protected: + static char const constexpr* const Contents1 = + "10.5.6.7/8\n" + "Austin Law Firm:216.16.1.144-216.16.1.151\n" + "Sargent Controls and Aerospace:216.19.18.0-216.19.18.255\n" + "Corel Corporation:216.21.157.192-216.21.157.223\n" + "Fox Speed Channel:216.79.131.192-216.79.131.223\n"; + + static char const constexpr* const Contents2 = + "10.5.6.7/8\n" + "Austin Law Firm:216.16.1.144-216.16.1.151\n" + "Sargent Controls and Aerospace:216.19.18.0-216.19.18.255\n" + "Corel Corporation:216.21.157.192-216.21.157.223\n" + "Fox Speed Channel:216.79.131.192-216.79.131.223\n" + "Evilcorp:216.88.88.0-216.88.88.255\n"; + +#if 0 + void createFileWithContents(char const* path, char const* contents) + { + tr_sys_file_t fd; + char* dir; + + dir = tr_sys_path_dirname(path, nullptr); + tr_sys_dir_create(dir, TR_SYS_DIR_CREATE_PARENTS, 0700, nullptr); + tr_free(dir); + + fd = tr_sys_file_open(path, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, 0600, nullptr); + blockingFileWrite(fd, contents, strlen(contents)); + tr_sys_file_close(fd, nullptr); + + sync(); + } + +#endif + + bool addressIsBlocked(char const* address_str) + { + struct tr_address addr = {}; + tr_address_from_string(&addr, address_str); + return tr_sessionIsAddressBlocked(session_, &addr); + } +}; + +TEST_F(BlocklistTest, parsing) +{ + EXPECT_EQ(0, tr_blocklistGetRuleCount(session_)); + + // init the blocklist + auto const path = makeString(tr_buildPath(tr_sessionGetConfigDir(session_), "blocklists", "level1", nullptr)); + createFileWithContents(path, Contents1); + tr_sessionReloadBlocklists(session_); + EXPECT_TRUE(tr_blocklistExists(session_)); + EXPECT_EQ(5, tr_blocklistGetRuleCount(session_)); + + // enable the blocklist + EXPECT_FALSE(tr_blocklistIsEnabled(session_)); + tr_blocklistSetEnabled(session_, true); + EXPECT_TRUE(tr_blocklistIsEnabled(session_)); + + // test blocked addresses + EXPECT_FALSE(addressIsBlocked("0.0.0.1")); + EXPECT_TRUE(addressIsBlocked("10.1.2.3")); + EXPECT_FALSE(addressIsBlocked("216.16.1.143")); + EXPECT_TRUE(addressIsBlocked("216.16.1.144")); + EXPECT_TRUE(addressIsBlocked("216.16.1.145")); + EXPECT_TRUE(addressIsBlocked("216.16.1.146")); + EXPECT_TRUE(addressIsBlocked("216.16.1.147")); + EXPECT_TRUE(addressIsBlocked("216.16.1.148")); + EXPECT_TRUE(addressIsBlocked("216.16.1.149")); + EXPECT_TRUE(addressIsBlocked("216.16.1.150")); + EXPECT_TRUE(addressIsBlocked("216.16.1.151")); + EXPECT_FALSE(addressIsBlocked("216.16.1.152")); + EXPECT_FALSE(addressIsBlocked("216.16.1.153")); + EXPECT_FALSE(addressIsBlocked("217.0.0.1")); + EXPECT_FALSE(addressIsBlocked("255.0.0.1")); +} + +/*** +**** +***/ + +TEST_F(BlocklistTest, updating) +{ + // init the session + char* path = tr_buildPath(tr_sessionGetConfigDir(session_), "blocklists", "level1", nullptr); + + // no blocklist to start with... + EXPECT_EQ(0, tr_blocklistGetRuleCount(session_)); + + // test that updated source files will get loaded + createFileWithContents(path, Contents1); + tr_sessionReloadBlocklists(session_); + EXPECT_EQ(5, tr_blocklistGetRuleCount(session_)); + + // test that updated source files will get loaded + createFileWithContents(path, Contents2); + tr_sessionReloadBlocklists(session_); + EXPECT_EQ(6, tr_blocklistGetRuleCount(session_)); + + // test that updated source files will get loaded + createFileWithContents(path, Contents1); + tr_sessionReloadBlocklists(session_); + EXPECT_EQ(5, tr_blocklistGetRuleCount(session_)); + + // ensure that new files, if bad, get skipped + createFileWithContents(path, "# nothing useful\n"); + tr_sessionReloadBlocklists(session_); + EXPECT_EQ(5, tr_blocklistGetRuleCount(session_)); + + // cleanup + tr_free(path); +} + +} // namespace test + +} // namespace libtransmission diff --git a/tests/libtransmission/clients-test.cc b/tests/libtransmission/clients-test.cc new file mode 100644 index 000000000..618530647 --- /dev/null +++ b/tests/libtransmission/clients-test.cc @@ -0,0 +1,72 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include + +#include "transmission.h" +#include "clients.h" + +#include "gtest/gtest.h" + +TEST(Client, clientForId) +{ + struct Test + { + char const* peer_id; + char const* expected_client; + }; + + auto constexpr Tests = std::array{ + Test{ "-BT791B-", "BitTorrent 7.9.1 (Beta)" }, + { "-BT791\0-", "BitTorrent 7.9.1" }, + { "-FC1013-", "FileCroc 1.0.1.3" }, + { "-FC1013-", "FileCroc 1.0.1.3" }, + { "-MR1100-", "Miro 1.1.0.0" }, + { "-TR0006-", "Transmission 0.6" }, + { "-TR0072-", "Transmission 0.72" }, + { "-TR111Z-", "Transmission 1.11+" }, + { "-UT341\0-", "\xc2\xb5Torrent 3.4.1" }, + { "O1008132", "Osprey 1.0.0" }, + { "TIX0193-", "Tixati 1.93" }, + + /* Xfplay 9.9.92 to 9.9.94 uses "-XF9992-" */ + { "-XF9992-", "Xfplay 9.9.92" }, + + /* Older Xfplay versions have three digit version number */ + { "-XF9990-", "Xfplay 9.9.9" }, + + /* PicoTorrent */ + { "-PI0091-", "PicoTorrent 0.09.1" }, + { "-PI0120-", "PicoTorrent 0.12.0" }, + + /* Free Download Manager */ + { "-FD51R\xFF-", "Free Download Manager 5.1.27" }, + { "-FD51W\xFF-", "Free Download Manager 5.1.32" }, + { "-FD51@\xFF-", "Free Download Manager 5.1.x" }, /* Negative test case */ + + /* Folx */ + { "-FL51FF-", "Folx 5.x" }, /* Folx v5.2.1.13690 */ + + /* Baidu Netdisk */ + { "-BN0001-", "Baidu Netdisk" }, /* Baidu Netdisk Client v5.5.4 */ + + /* gobbledygook */ + { "-IIO\x10\x2D\x04-", "-IIO%10-%04-" }, + { "-I\05O\x08\x03\x01-", "-I%05O%08%03%01-" }, + + { "\x65\x78\x62\x63\x00\x38\x7A\x44\x63\x10\x2D\x6E\x9A\xD6\x72\x3B\x33\x9F\x35\xA9", "BitComet 0.56" }, + { "\x65\x78\x62\x63\x00\x38\x4C\x4F\x52\x44\x32\x00\x04\x8E\xCE\xD5\x7B\xD7\x10\x28", "BitLord 0.56" } + }; + + for (auto const& test : Tests) + { + auto buf = std::array{}; + tr_clientForId(buf.data(), buf.size(), test.peer_id); + EXPECT_STREQ(test.expected_client, buf.data()); + } +} diff --git a/libtransmission/crypto-test-ref.h b/tests/libtransmission/crypto-test-ref.h similarity index 100% rename from libtransmission/crypto-test-ref.h rename to tests/libtransmission/crypto-test-ref.h diff --git a/tests/libtransmission/crypto-test.cc b/tests/libtransmission/crypto-test.cc new file mode 100644 index 000000000..fdf6a4621 --- /dev/null +++ b/tests/libtransmission/crypto-test.cc @@ -0,0 +1,278 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "crypto.h" +#include "crypto-utils.h" +#include "utils.h" + +#include "crypto-test-ref.h" + +#include "gtest/gtest.h" + +#include +#include +#include +#include + +TEST(Crypto, torrentHash) +{ + tr_crypto a; + + auto hash = std::array{}; + for (size_t i = 0; i < hash.size(); ++i) + { + hash[i] = uint8_t(i); + } + + tr_cryptoConstruct(&a, nullptr, true); + + EXPECT_FALSE(tr_cryptoHasTorrentHash(&a)); + EXPECT_EQ(nullptr, tr_cryptoGetTorrentHash(&a)); + + tr_cryptoSetTorrentHash(&a, hash.data()); + EXPECT_TRUE(tr_cryptoHasTorrentHash(&a)); + EXPECT_NE(nullptr, tr_cryptoGetTorrentHash(&a)); + EXPECT_EQ(0, memcmp(tr_cryptoGetTorrentHash(&a), hash.data(), hash.size())); + + tr_cryptoDestruct(&a); + + for (size_t i = 0; i < hash.size(); ++i) + { + hash[i] = uint8_t(i + 1); + } + + tr_cryptoConstruct(&a, hash.data(), false); + + EXPECT_TRUE(tr_cryptoHasTorrentHash(&a)); + EXPECT_NE(nullptr, tr_cryptoGetTorrentHash(&a)); + EXPECT_EQ(0, memcmp(tr_cryptoGetTorrentHash(&a), hash.data(), hash.size())); + + tr_cryptoSetTorrentHash(&a, nullptr); + EXPECT_FALSE(tr_cryptoHasTorrentHash(&a)); + EXPECT_EQ(nullptr, tr_cryptoGetTorrentHash(&a)); + + tr_cryptoDestruct(&a); +} + +TEST(Crypto, encryptDecrypt) +{ + auto hash = std::array{}; + for (size_t i = 0; i < hash.size(); ++i) + { + hash[i] = uint8_t(i); + } + + auto a = tr_crypto {}; + tr_cryptoConstruct(&a, hash.data(), false); + auto b = tr_crypto_ {}; + tr_cryptoConstruct_(&b, hash.data(), true); + auto public_key_length = int{}; + EXPECT_TRUE(tr_cryptoComputeSecret(&a, tr_cryptoGetMyPublicKey_(&b, &public_key_length))); + EXPECT_TRUE(tr_cryptoComputeSecret_(&b, tr_cryptoGetMyPublicKey(&a, &public_key_length))); + + auto const input1 = std::string { "test1" }; + auto encrypted1 = std::array{}; + auto decrypted1 = std::array{}; + + tr_cryptoEncryptInit(&a); + tr_cryptoEncrypt(&a, input1.size(), input1.data(), encrypted1.data()); + tr_cryptoDecryptInit_(&b); + tr_cryptoDecrypt_(&b, input1.size(), encrypted1.data(), decrypted1.data()); + EXPECT_EQ(input1, std::string(decrypted1.data(), input1.size())); + + auto const input2 = std::string { "@#)C$@)#(*%bvkdjfhwbc039bc4603756VB3)" }; + auto encrypted2 = std::array{}; + auto decrypted2 = std::array{}; + + tr_cryptoEncryptInit_(&b); + tr_cryptoEncrypt_(&b, input2.size(), input2.data(), encrypted2.data()); + tr_cryptoDecryptInit(&a); + tr_cryptoDecrypt(&a, input2.size(), encrypted2.data(), decrypted2.data()); + EXPECT_EQ(input2, std::string(decrypted2.data(), input2.size())); + + tr_cryptoDestruct_(&b); + tr_cryptoDestruct(&a); +} + +TEST(Crypto, sha1) +{ + auto hash1 = std::array{}; + auto hash2 = std::array{}; + + EXPECT_TRUE(tr_sha1(hash1.data(), "test", 4, nullptr)); + EXPECT_TRUE(tr_sha1_(hash2.data(), "test", 4, nullptr)); + EXPECT_EQ(0, + memcmp(hash1.data(), "\xa9\x4a\x8f\xe5\xcc\xb1\x9b\xa6\x1c\x4c\x08\x73\xd3\x91\xe9\x87\x98\x2f\xbb\xd3", + hash1.size())); + EXPECT_EQ(0, memcmp(hash1.data(), hash2.data(), hash2.size())); + + EXPECT_TRUE(tr_sha1(hash1.data(), "1", 1, "22", 2, "333", 3, nullptr)); + EXPECT_TRUE(tr_sha1_(hash2.data(), "1", 1, "22", 2, "333", 3, nullptr)); + EXPECT_EQ(0, + memcmp(hash1.data(), "\x1f\x74\x64\x8e\x50\xa6\xa6\x70\x8e\xc5\x4a\xb3\x27\xa1\x63\xd5\x53\x6b\x7c\xed", + hash1.size())); + EXPECT_EQ(0, memcmp(hash1.data(), hash2.data(), hash2.size())); +} + +TEST(Crypto, ssha1) +{ + struct Test + { + char const* const plain_text; + char const* const ssha1; + }; + + auto constexpr Tests = std::array{ + Test{ "test", "{15ad0621b259a84d24dcd4e75b09004e98a3627bAMbyRHJy" }, + { "QNY)(*#$B)!_X$B !_B#($^!)*&$%CV!#)&$C!@$(P*)", "{10e2d7acbb104d970514a147cd16d51dfa40fb3c0OSwJtOL" } + }; + + auto constexpr HashCount = size_t{ 4 * 1024 }; + + for (auto const& test : Tests) + { + std::unordered_set hashes; + hashes.reserve(HashCount); + + char* const phrase = tr_strdup(test.plain_text); + EXPECT_TRUE(tr_ssha1_matches(test.ssha1, phrase)); + EXPECT_TRUE(tr_ssha1_matches_(test.ssha1, phrase)); + + for (size_t j = 0; j < HashCount; ++j) + { + char* hash = (j % 2 == 0) ? tr_ssha1(phrase) : tr_ssha1_(phrase); + EXPECT_NE(nullptr, hash); + + // phrase matches each of generated hashes + EXPECT_TRUE(tr_ssha1_matches(hash, phrase)); + EXPECT_TRUE(tr_ssha1_matches_(hash, phrase)); + + hashes.insert(hash); + tr_free(hash); + } + + // confirm all hashes are different + EXPECT_EQ(HashCount, hashes.size()); + + /* exchange two first chars */ + phrase[0] ^= phrase[1]; + phrase[1] ^= phrase[0]; + phrase[0] ^= phrase[1]; + + for (auto const& hash : hashes) + { + /* changed phrase doesn't match the hashes */ + EXPECT_FALSE(tr_ssha1_matches(hash.c_str(), phrase)); + EXPECT_FALSE(tr_ssha1_matches_(hash.c_str(), phrase)); + } + + tr_free(phrase); + } + + /* should work with different salt lengths as well */ + EXPECT_TRUE(tr_ssha1_matches("{a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "test")); + EXPECT_TRUE(tr_ssha1_matches("{d209a21d3bc4f8fc4f8faf347e69f3def597eb170pySy4ai1ZPMjeU1", "test")); +} + +TEST(Crypto, random) +{ + /* test that tr_rand_int() stays in-bounds */ + for (int i = 0; i < 100000; ++i) + { + int const val = tr_rand_int(100); + EXPECT_LE(0, val); + EXPECT_LT(val, 100); + } +} + +static bool base64Eq(char const* a, char const* b) +{ + for (;; ++a, ++b) + { + while (*a == '\r' || *a == '\n') + { + ++a; + } + + while (*b == '\r' || *b == '\n') + { + ++b; + } + + if (*a == '\0' || *b == '\0' || *a != *b) + { + break; + } + } + + return *a == *b; +} + +TEST(Crypto, base64) +{ + auto len = size_t{}; + auto* out = static_cast(tr_base64_encode_str("YOYO!", &len)); + EXPECT_EQ(strlen(out), len); + EXPECT_TRUE(base64Eq("WU9ZTyE=", out)); + auto* in = static_cast(tr_base64_decode_str(out, &len)); + EXPECT_EQ(decltype(len) { 5 }, len); + EXPECT_STREQ("YOYO!", in); + tr_free(in); + tr_free(out); + + out = static_cast(tr_base64_encode("", 0, &len)); + EXPECT_EQ(size_t{}, len); + EXPECT_STREQ("", out); + tr_free(out); + out = static_cast(tr_base64_decode("", 0, &len)); + EXPECT_EQ(0, len); + EXPECT_STREQ("", out); + tr_free(out); + + out = static_cast(tr_base64_encode(nullptr, 0, &len)); + EXPECT_EQ(0, len); + EXPECT_EQ(nullptr, out); + out = static_cast(tr_base64_decode(nullptr, 0, &len)); + EXPECT_EQ(0, len); + EXPECT_EQ(nullptr, out); + + static auto constexpr MaxBufSize = size_t{ 1024 }; + for (size_t i = 1; i <= MaxBufSize; ++i) + { + auto buf = std::array{}; + + for (size_t j = 0; j < i; ++j) + { + buf[j] = char(tr_rand_int_weak(256)); + } + + out = static_cast(tr_base64_encode(buf.data(), i, &len)); + EXPECT_EQ(strlen(out), len); + in = static_cast(tr_base64_decode(out, len, &len)); + EXPECT_EQ(i, len); + EXPECT_EQ(0, memcmp(in, buf.data(), len)); + tr_free(in); + tr_free(out); + + for (size_t j = 0; j < i; ++j) + { + buf[j] = char(1 + tr_rand_int_weak(255)); + } + + buf[i] = '\0'; + + out = static_cast(tr_base64_encode_str(buf.data(), &len)); + EXPECT_EQ(strlen(out), len); + in = static_cast(tr_base64_decode_str(out, &len)); + EXPECT_EQ(i, len); + EXPECT_STREQ(buf.data(), in); + tr_free(in); + tr_free(out); + } +} diff --git a/tests/libtransmission/error-test.cc b/tests/libtransmission/error-test.cc new file mode 100644 index 000000000..01fe7155e --- /dev/null +++ b/tests/libtransmission/error-test.cc @@ -0,0 +1,66 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "error.h" + +#include "gtest/gtest.h" + +TEST(Error, errorSet) +{ + tr_error* err = nullptr; + + tr_error_prefix(&err, "error: "); + EXPECT_EQ(nullptr, err); + + tr_error_set(&err, 1, "error: %s (%d)", "oops", 2); + EXPECT_NE(nullptr, err); + EXPECT_EQ(1, err->code); + EXPECT_STREQ("error: oops (2)", err->message); + tr_error_clear(&err); + EXPECT_EQ(nullptr, err); + + tr_error_set_literal(&err, 2, "oops"); + EXPECT_NE(nullptr, err); + EXPECT_EQ(2, err->code); + EXPECT_STREQ("oops", err->message); + + tr_error_prefix(&err, "error: "); + EXPECT_NE(nullptr, err); + EXPECT_EQ(2, err->code); + EXPECT_STREQ("error: oops", err->message); + + tr_error_free(err); +} + +TEST(Error, propagate) +{ + tr_error* err = nullptr; + tr_error* err2 = nullptr; + + tr_error_set_literal(&err, 1, "oops"); + EXPECT_NE(nullptr, err); + EXPECT_EQ(1, err->code); + EXPECT_STREQ("oops", err->message); + + tr_error_propagate(&err2, &err); + EXPECT_NE(nullptr, err2); + EXPECT_EQ(1, err2->code); + EXPECT_STREQ("oops", err2->message); + EXPECT_EQ(nullptr, err); + + tr_error_propagate_prefixed(&err, &err2, "error: "); + EXPECT_NE(nullptr, err); + EXPECT_EQ(1, err->code); + EXPECT_STREQ("error: oops", err->message); + EXPECT_EQ(nullptr, err2); + + tr_error_propagate(nullptr, &err); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(nullptr, err2); +} diff --git a/tests/libtransmission/file-test.cc b/tests/libtransmission/file-test.cc new file mode 100644 index 000000000..79cb278b1 --- /dev/null +++ b/tests/libtransmission/file-test.cc @@ -0,0 +1,1498 @@ +/* + * 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 "transmission.h" +#include "error.h" +#include "file.h" + +#include "test-fixtures.h" + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#endif + +#if !defined(__OpenBSD__) +#define HAVE_UNIFIED_BUFFER_CACHE +#endif + +#ifndef _WIN32 +#define NATIVE_PATH_SEP "/" +#else +#define NATIVE_PATH_SEP "\\" +#endif + +namespace libtransmission +{ + +namespace test +{ + +class FileTest : public SessionTest +{ +protected: + auto createTestDir(std::string const& child_name) + { + auto test_dir = makeString(tr_buildPath(tr_sessionGetConfigDir(session_), child_name.c_str(), nullptr)); + tr_sys_dir_create(test_dir.data(), 0, 0777, nullptr); + return test_dir; + } + + bool createSymlink(char const* dst_path, char const* src_path, bool dst_is_dir) + { +#ifndef _WIN32 + + (void)dst_is_dir; + + return symlink(src_path, dst_path) != -1; + +#else + wchar_t* wide_src_path = tr_win32_utf8_to_native(src_path, -1); + wchar_t* wide_dst_path = tr_win32_utf8_to_native(dst_path, -1); + + auto const ret = CreateSymbolicLinkW(wide_dst_path, wide_src_path, dst_is_dir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0); + + tr_free(wide_dst_path); + tr_free(wide_src_path); + + return ret; + +#endif + } + + bool createHardlink(char const* dst_path, char const* src_path) + { +#ifndef _WIN32 + + return link(src_path, dst_path) != -1; + +#else + + wchar_t* wide_src_path = tr_win32_utf8_to_native(src_path, -1); + wchar_t* wide_dst_path = tr_win32_utf8_to_native(dst_path, -1); + + auto const ret = CreateHardLinkW(wide_dst_path, wide_src_path, nullptr); + + tr_free(wide_dst_path); + tr_free(wide_src_path); + + return ret; + +#endif + } + + void clearPathInfo(tr_sys_path_info* info) + { + *info = {}; + } + + bool pathContainsNoSymlinks(char const* path) + { + char const* p = path; + + while (*p != '\0') + { + tr_sys_path_info info; + char const* slash_pos = strchr(p, '/'); + +#ifdef _WIN32 + + char const* backslash_pos = strchr(p, '\\'); + + if (slash_pos == nullptr || (backslash_pos != nullptr && backslash_pos < slash_pos)) + { + slash_pos = backslash_pos; + } + +#endif + + if (slash_pos == nullptr) + { + slash_pos = p + strlen(p) - 1; + } + + auto const path_part = makeString(tr_strndup(path, size_t(slash_pos - path + 1))); + + if (!tr_sys_path_get_info(path_part.c_str(), TR_SYS_PATH_NO_FOLLOW, &info, nullptr) || + (info.type != TR_SYS_PATH_IS_FILE && info.type != TR_SYS_PATH_IS_DIRECTORY)) + { + return false; + } + + p = slash_pos + 1; + } + + return true; + } + + bool validatePermissions(char const* path, unsigned int permissions) + { +#ifndef _WIN32 + + struct stat sb = {}; + return stat(path, &sb) != -1 && (sb.st_mode & 0777) == permissions; + +#else + + (void)path; + (void)permissions; + + /* No UNIX permissions on Windows */ + return true; + +#endif + } + + struct XnameTestData + { + char const* input; + char const* output; + }; + + void testPathXname(XnameTestData const* data, size_t data_size, char* (*func)(char const*, tr_error**)) + { + for (size_t i = 0; i < data_size; ++i) + { + tr_error* err = nullptr; + char* name = func(data[i].input, &err); + + if (data[i].output != nullptr) + { + EXPECT_NE(nullptr, name); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ(data[i].output, name); + tr_free(name); + } + else + { + EXPECT_EQ(nullptr, name); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + } + } + } + + static void testDirReadImpl(std::string const& path, bool* have1, bool* have2) + { + *have1 = *have2 = false; + + tr_error* err = nullptr; + auto dd = tr_sys_dir_open(path.c_str(), &err); + EXPECT_NE(TR_BAD_SYS_DIR, dd); + EXPECT_EQ(nullptr, err); + + char const* name; + while ((name = tr_sys_dir_read_name(dd, &err)) != nullptr) + { + EXPECT_EQ(nullptr, err); + + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + { + continue; + } + + if (strcmp(name, "a") == 0) + { + *have1 = true; + } + else if (strcmp(name, "b") == 0) + { + *have2 = true; + } + else + { + FAIL(); + } + } + + EXPECT_EQ(nullptr, err); + + EXPECT_TRUE(tr_sys_dir_close(dd, &err)); + EXPECT_EQ(nullptr, err); + } +}; + +TEST_F(FileTest, getInfo) +{ + auto const test_dir = createTestDir(currentTestName()); + tr_sys_path_info info; + + char* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + char* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + + // Can't get info of non-existent file/directory + tr_error* err = nullptr; + EXPECT_FALSE(tr_sys_path_get_info(path1, 0, &info, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + auto t = time(nullptr); + createFileWithContents(path1, "test"); + + // Good file info + clearPathInfo(&info); + EXPECT_TRUE(tr_sys_path_get_info(path1, 0, &info, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(TR_SYS_PATH_IS_FILE, info.type); + EXPECT_EQ(4, info.size); + EXPECT_GT(info.last_modified_at, t - 1); + EXPECT_LE(info.last_modified_at, time(nullptr) + 1); + + // Good file info (by handle) + auto fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0, nullptr); + clearPathInfo(&info); + EXPECT_TRUE(tr_sys_file_get_info(fd, &info, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(TR_SYS_PATH_IS_FILE, info.type); + EXPECT_EQ(4, info.size); + EXPECT_GT(info.last_modified_at, t - 1); + EXPECT_LE(info.last_modified_at, time(nullptr) + 1); + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + + // Good directory info + t = time(nullptr); + tr_sys_dir_create(path1, 0, 0777, nullptr); + clearPathInfo(&info); + EXPECT_TRUE(tr_sys_path_get_info(path1, 0, &info, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(TR_SYS_PATH_IS_DIRECTORY, info.type); + EXPECT_NE(uint64_t(-1), info.size); + EXPECT_GE(info.last_modified_at, t - 1); + EXPECT_LE(info.last_modified_at, time(nullptr) + 1); + tr_sys_path_remove(path1, nullptr); + + if (createSymlink(path1, path2, false)) + { + // Can't get info of non-existent file/directory + EXPECT_FALSE(tr_sys_path_get_info(path1, 0, &info, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + t = time(nullptr); + createFileWithContents(path2, "test"); + + // Good file info + clearPathInfo(&info); + EXPECT_TRUE(tr_sys_path_get_info(path1, 0, &info, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(TR_SYS_PATH_IS_FILE, info.type); + EXPECT_EQ(4, info.size); + EXPECT_GE(info.last_modified_at, t - 1); + EXPECT_LE(info.last_modified_at, time(nullptr) + 1); + + // Good file info (by handle) + fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0, nullptr); + clearPathInfo(&info); + EXPECT_TRUE(tr_sys_file_get_info(fd, &info, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(TR_SYS_PATH_IS_FILE, info.type); + EXPECT_EQ(4, info.size); + EXPECT_GE(info.last_modified_at, t - 1); + EXPECT_LE(info.last_modified_at, time(nullptr) + 1); + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + + // Good directory info + t = time(nullptr); + tr_sys_dir_create(path2, 0, 0777, nullptr); + EXPECT_TRUE(createSymlink(path1, path2, true)); /* Win32: directory and file symlinks differ :( */ + clearPathInfo(&info); + EXPECT_TRUE(tr_sys_path_get_info(path1, 0, &info, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(TR_SYS_PATH_IS_DIRECTORY, info.type); + EXPECT_NE(uint64_t(-1), info.size); + EXPECT_GE(info.last_modified_at, t - 1); + EXPECT_LE(info.last_modified_at, time(nullptr) + 1); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); + } + + tr_free(path2); + tr_free(path1); +} + +TEST_F(FileTest, pathExists) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + + // Non-existent file does not exist + tr_error* err = nullptr; + EXPECT_FALSE(tr_sys_path_exists(path1, &err)); + EXPECT_EQ(nullptr, err); + + // Create file and see that it exists + createFileWithContents(path1, "test"); + EXPECT_TRUE(tr_sys_path_exists(path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path1, nullptr); + + // Create directory and see that it exists + tr_sys_dir_create(path1, 0, 0777, nullptr); + EXPECT_TRUE(tr_sys_path_exists(path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path1, nullptr); + + if (createSymlink(path1, path2, false)) + { + // Non-existent file does not exist (via symlink) + EXPECT_FALSE(tr_sys_path_exists(path1, &err)); + EXPECT_EQ(nullptr, err); + + // Create file and see that it exists (via symlink) + createFileWithContents(path2, "test"); + EXPECT_TRUE(tr_sys_path_exists(path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + + /* Create directory and see that it exists (via symlink) */ + tr_sys_dir_create(path2, 0, 0777, nullptr); + EXPECT_TRUE(createSymlink(path1, path2, true)); /* Win32: directory and file symlinks differ :( */ + EXPECT_TRUE(tr_sys_path_exists(path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); + } + + tr_free(path2); + tr_free(path1); +} + +TEST_F(FileTest, pathIsRelative) +{ + EXPECT_TRUE(tr_sys_path_is_relative("")); + EXPECT_TRUE(tr_sys_path_is_relative(".")); + EXPECT_TRUE(tr_sys_path_is_relative("..")); + EXPECT_TRUE(tr_sys_path_is_relative("x")); + EXPECT_TRUE(tr_sys_path_is_relative("\\")); + EXPECT_TRUE(tr_sys_path_is_relative(":")); + +#ifdef _WIN32 + + EXPECT_TRUE(tr_sys_path_is_relative("/")); + EXPECT_TRUE(tr_sys_path_is_relative("\\x")); + EXPECT_TRUE(tr_sys_path_is_relative("/x")); + EXPECT_TRUE(tr_sys_path_is_relative("\\x\\y")); + EXPECT_TRUE(tr_sys_path_is_relative("/x/y")); + EXPECT_TRUE(tr_sys_path_is_relative("C:x")); + EXPECT_TRUE(tr_sys_path_is_relative("C:x\\y")); + EXPECT_TRUE(tr_sys_path_is_relative("C:x/y")); + + EXPECT_FALSE(tr_sys_path_is_relative("\\\\")); + EXPECT_FALSE(tr_sys_path_is_relative("//")); + EXPECT_FALSE(tr_sys_path_is_relative("\\\\x")); + EXPECT_FALSE(tr_sys_path_is_relative("//x")); + EXPECT_FALSE(tr_sys_path_is_relative("\\\\x\\y")); + EXPECT_FALSE(tr_sys_path_is_relative("//x/y")); + EXPECT_FALSE(tr_sys_path_is_relative("\\\\.\\x")); + EXPECT_FALSE(tr_sys_path_is_relative("//./x")); + + EXPECT_FALSE(tr_sys_path_is_relative("a:")); + EXPECT_FALSE(tr_sys_path_is_relative("a:\\")); + EXPECT_FALSE(tr_sys_path_is_relative("a:/")); + EXPECT_FALSE(tr_sys_path_is_relative("Z:")); + EXPECT_FALSE(tr_sys_path_is_relative("Z:\\")); + EXPECT_FALSE(tr_sys_path_is_relative("Z:/")); + +#else /* _WIN32 */ + + EXPECT_FALSE(tr_sys_path_is_relative("/")); + EXPECT_FALSE(tr_sys_path_is_relative("/x")); + EXPECT_FALSE(tr_sys_path_is_relative("/x/y")); + EXPECT_FALSE(tr_sys_path_is_relative("//x")); + +#endif /* _WIN32 */ +} + +TEST_F(FileTest, pathIsSame) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + auto* path3 = tr_buildPath(path2, "c", nullptr); + + /* Two non-existent files are not the same */ + tr_error* err = nullptr; + EXPECT_FALSE(tr_sys_path_is_same(path1, path1, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + + /* Two same files are the same */ + createFileWithContents(path1, "test"); + EXPECT_TRUE(tr_sys_path_is_same(path1, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Existent and non-existent files are not the same */ + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Two separate files (even with same content) are not the same */ + createFileWithContents(path2, "test"); + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path1, nullptr); + + /* Two same directories are the same */ + tr_sys_dir_create(path1, 0, 0777, nullptr); + EXPECT_TRUE(tr_sys_path_is_same(path1, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* File and directory are not the same */ + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + + /* Two separate directories are not the same */ + tr_sys_dir_create(path2, 0, 0777, nullptr); + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path1, nullptr); + tr_sys_path_remove(path2, nullptr); + + if (createSymlink(path1, ".", true)) + { + /* Directory and symlink pointing to it are the same */ + EXPECT_TRUE(tr_sys_path_is_same(path1, test_dir.data(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_path_is_same(test_dir.data(), path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Non-existent file and symlink are not the same */ + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Symlinks pointing to different directories are not the same */ + createSymlink(path2, "..", true); + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + + /* Symlinks pointing to same directory are the same */ + createSymlink(path2, ".", true); + EXPECT_TRUE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + + /* Directory and symlink pointing to another directory are not the same */ + tr_sys_dir_create(path2, 0, 0777, nullptr); + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Symlinks pointing to same directory are the same */ + createSymlink(path3, "..", true); + EXPECT_TRUE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path1, nullptr); + + /* File and symlink pointing to directory are not the same */ + createFileWithContents(path1, "test"); + EXPECT_FALSE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path3, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path3, nullptr); + + /* File and symlink pointing to same file are the same */ + createSymlink(path3, path1, false); + EXPECT_TRUE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_path_is_same(path3, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Symlinks pointing to non-existent files are not the same */ + tr_sys_path_remove(path1, nullptr); + createSymlink(path1, "missing", false); + tr_sys_path_remove(path3, nullptr); + createSymlink(path3, "missing", false); + EXPECT_FALSE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path3, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path3, nullptr); + + /* Symlinks pointing to same non-existent file are not the same */ + createSymlink(path3, ".." NATIVE_PATH_SEP "missing", false); + EXPECT_FALSE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path3, path1, &err)); + EXPECT_EQ(nullptr, err); + + /* Non-existent file and symlink pointing to non-existent file are not the same */ + tr_sys_path_remove(path3, nullptr); + EXPECT_FALSE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path3, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); + } + + tr_free(path3); + path3 = tr_buildPath(test_dir.data(), "c", nullptr); + + createFileWithContents(path1, "test"); + + if (createHardlink(path2, path1)) + { + /* File and hardlink to it are the same */ + EXPECT_TRUE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + + /* Two hardlinks to the same file are the same */ + createHardlink(path3, path2); + EXPECT_TRUE(tr_sys_path_is_same(path2, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + + EXPECT_TRUE(tr_sys_path_is_same(path1, path3, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path3, nullptr); + + /* File and hardlink to another file are not the same */ + createFileWithContents(path3, "test"); + createHardlink(path2, path3); + EXPECT_FALSE(tr_sys_path_is_same(path1, path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_is_same(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path3, nullptr); + tr_sys_path_remove(path2, nullptr); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run hardlink tests\n", __FUNCTION__); + } + + if (createSymlink(path2, path1, false) && createHardlink(path3, path1)) + { + EXPECT_TRUE(tr_sys_path_is_same(path2, path3, &err)); + EXPECT_EQ(nullptr, err); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run combined symlink and hardlink tests\n", __FUNCTION__); + } + + tr_sys_path_remove(path3, nullptr); + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + + tr_free(path3); + tr_free(path2); + tr_free(path1); +} + +TEST_F(FileTest, pathResolve) +{ + auto const test_dir = createTestDir(currentTestName()); + + tr_error* err = nullptr; + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + + createFileWithContents(path1, "test"); + + if (createSymlink(path2, path1, false)) + { + auto tmp = makeString(tr_sys_path_resolve(path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(pathContainsNoSymlinks(tmp.c_str())); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + + tr_sys_dir_create(path1, 0, 0755, nullptr); + EXPECT_TRUE(createSymlink(path2, path1, true)); /* Win32: directory and file symlinks differ :( */ + tmp = makeString(tr_sys_path_resolve(path2, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(pathContainsNoSymlinks(tmp.c_str())); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); + } + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + + tr_free(path2); + tr_free(path1); + +#ifdef _WIN32 + + { + char* tmp; + + tmp = tr_sys_path_resolve("\\\\127.0.0.1\\NonExistent", &err); + EXPECT_EQ(nullptr, tmp); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + tmp = tr_sys_path_resolve("\\\\127.0.0.1\\ADMIN$\\NonExistent", &err); + EXPECT_EQ(nullptr, tmp); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + tmp = tr_sys_path_resolve("\\\\127.0.0.1\\ADMIN$\\System32", &err); + EXPECT_STREQ("\\\\127.0.0.1\\ADMIN$\\System32", tmp); + EXPECT_EQ(nullptr, err); + tr_free(tmp); + } + +#endif +} + +TEST_F(FileTest, pathBasenameDirname) +{ + auto const common_xname_tests = std::vector{ + XnameTestData{ "/", "/" }, + { "", "." }, +#ifdef _WIN32 + { + "\\", "/" + }, + /* Invalid paths */ + { "\\\\\\", nullptr }, + { "123:", nullptr }, + /* Reserved characters */ + { "<", nullptr }, + { ">", nullptr }, + { ":", nullptr }, + { "\"", nullptr }, + { "|", nullptr }, + { "?", nullptr }, + { "*", nullptr }, + { "a\\<", nullptr }, + { "a\\>", nullptr }, + { "a\\:", nullptr }, + { "a\\\"", nullptr }, + { "a\\|", nullptr }, + { "a\\?", nullptr }, + { "a\\*", nullptr }, + { "c:\\a\\bc\\d", nullptr }, + { "c:\\a\\b:c\\d", nullptr }, + { "c:\\a\\b\"c\\d", nullptr }, + { "c:\\a\\b|c\\d", nullptr }, + { "c:\\a\\b?c\\d", nullptr }, + { "c:\\a\\b*c\\d", nullptr } +#else + { + "////", "/" + } +#endif + }; + + testPathXname(common_xname_tests.data(), common_xname_tests.size(), tr_sys_path_basename); + testPathXname(common_xname_tests.data(), common_xname_tests.size(), tr_sys_path_dirname); + + auto const basename_tests = std::vector{ + XnameTestData{ "a", "a" }, + { "aa", "aa" }, + { "/aa", "aa" }, + { "/a/b/c", "c" }, + { "/a/b/c/", "c" }, +#ifdef _WIN32 + { + "c:\\a\\b\\c", "c" + }, + { "c:", "/" }, + { "c:/", "/" }, + { "c:\\", "/" }, + { "c:a/b", "b" }, + { "c:a", "a" }, + { "\\\\a\\b\\c", "c" }, + { "//a/b", "b" }, + { "//1.2.3.4/b", "b" }, + { "\\\\a", "a" }, + { "\\\\1.2.3.4", "1.2.3.4" }, + { "\\", "/" }, + { "\\a", "a" } +#endif + }; + + testPathXname(basename_tests.data(), basename_tests.size(), tr_sys_path_basename); + + auto const dirname_tests = std::vector{ + XnameTestData{ "/a/b/c", "/a/b" }, + { "a/b/c", "a/b" }, + { "a/b/c/", "a/b" }, + { "a", "." }, + { "a/", "." }, +#ifdef _WIN32 + { + "C:\\a/b\\c", "C:\\a/b" + }, + { "C:\\a/b\\c\\", "C:\\a/b" }, + { "C:\\a/b", "C:\\a" }, + { "C:/a", "C:" }, + { "C:", "C:" }, + { "C:/", "C:" }, + { "C:\\", "C:" }, + { "c:a/b", "c:a" }, + { "c:a", "c:." }, + { "c:.", "c:." }, + { "\\\\a\\b\\c", "\\\\a\\b" }, + { "\\\\a\\b\\c/", "\\\\a\\b" }, + { "//a/b", "//a" }, + { "//1.2.3.4/b", "//1.2.3.4" }, + { "\\\\a", "\\\\" }, + { "\\\\1.2.3.4", "\\\\" }, + { "\\\\", "\\\\" }, + { "a/b\\c", "a/b" } +#endif + }; + + testPathXname(dirname_tests.data(), dirname_tests.size(), tr_sys_path_dirname); + + /* TODO: is_same(dirname(x) + '/' + basename(x), x) */ +} + +TEST_F(FileTest, pathRename) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + auto* path3 = tr_buildPath(path2, "c", nullptr); + + createFileWithContents(path1, "test"); + + /* Preconditions */ + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_FALSE(tr_sys_path_exists(path2, nullptr)); + + /* Forward rename works */ + tr_error* err = nullptr; + EXPECT_TRUE(tr_sys_path_rename(path1, path2, &err)); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + EXPECT_TRUE(tr_sys_path_exists(path2, nullptr)); + EXPECT_EQ(nullptr, err); + + /* Backward rename works */ + EXPECT_TRUE(tr_sys_path_rename(path2, path1, &err)); + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_FALSE(tr_sys_path_exists(path2, nullptr)); + EXPECT_EQ(nullptr, err); + + /* Another backward rename [of non-existent file] does not work */ + EXPECT_FALSE(tr_sys_path_rename(path2, path1, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + /* Rename to file which couldn't be created does not work */ + EXPECT_FALSE(tr_sys_path_rename(path1, path3, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + /* Rename of non-existent file does not work */ + EXPECT_FALSE(tr_sys_path_rename(path3, path2, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + createFileWithContents(path2, "test"); + + /* Renaming file does overwrite existing file */ + EXPECT_TRUE(tr_sys_path_rename(path2, path1, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_dir_create(path2, 0, 0777, nullptr); + + /* Renaming file does not overwrite existing directory, and vice versa */ + EXPECT_FALSE(tr_sys_path_rename(path1, path2, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + EXPECT_FALSE(tr_sys_path_rename(path2, path1, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + tr_sys_path_remove(path2, nullptr); + + tr_free(path3); + path3 = tr_buildPath(test_dir.data(), "c", nullptr); + + if (createSymlink(path2, path1, false)) + { + /* Preconditions */ + EXPECT_TRUE(tr_sys_path_exists(path2, nullptr)); + EXPECT_FALSE(tr_sys_path_exists(path3, nullptr)); + EXPECT_TRUE(tr_sys_path_is_same(path1, path2, nullptr)); + + /* Rename of symlink works, files stay the same */ + EXPECT_TRUE(tr_sys_path_rename(path2, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path2, nullptr)); + EXPECT_TRUE(tr_sys_path_exists(path3, nullptr)); + EXPECT_TRUE(tr_sys_path_is_same(path1, path3, nullptr)); + + tr_sys_path_remove(path3, nullptr); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run symlink tests\n", __FUNCTION__); + } + + if (createHardlink(path2, path1)) + { + /* Preconditions */ + EXPECT_TRUE(tr_sys_path_exists(path2, nullptr)); + EXPECT_FALSE(tr_sys_path_exists(path3, nullptr)); + EXPECT_TRUE(tr_sys_path_is_same(path1, path2, nullptr)); + + /* Rename of hardlink works, files stay the same */ + EXPECT_TRUE(tr_sys_path_rename(path2, path3, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path2, nullptr)); + EXPECT_TRUE(tr_sys_path_exists(path3, nullptr)); + EXPECT_TRUE(tr_sys_path_is_same(path1, path3, nullptr)); + + tr_sys_path_remove(path3, nullptr); + } + else + { + fprintf(stderr, "WARNING: [%s] unable to run hardlink tests\n", __FUNCTION__); + } + + tr_sys_path_remove(path1, nullptr); + + tr_free(path3); + tr_free(path2); + tr_free(path1); +} + +TEST_F(FileTest, pathRemove) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + auto* path3 = tr_buildPath(path2, "c", nullptr); + + /* Can't remove non-existent file/directory */ + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + tr_error* err = nullptr; + EXPECT_FALSE(tr_sys_path_remove(path1, &err)); + EXPECT_NE(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + tr_error_clear(&err); + + /* Removing file works */ + createFileWithContents(path1, "test"); + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_TRUE(tr_sys_path_remove(path1, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + + /* Removing empty directory works */ + tr_sys_dir_create(path1, 0, 0777, nullptr); + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_TRUE(tr_sys_path_remove(path1, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + + /* Removing non-empty directory fails */ + tr_sys_dir_create(path2, 0, 0777, nullptr); + createFileWithContents(path3, "test"); + EXPECT_TRUE(tr_sys_path_exists(path2, nullptr)); + EXPECT_TRUE(tr_sys_path_exists(path3, nullptr)); + EXPECT_FALSE(tr_sys_path_remove(path2, &err)); + EXPECT_NE(nullptr, err); + EXPECT_TRUE(tr_sys_path_exists(path2, nullptr)); + EXPECT_TRUE(tr_sys_path_exists(path3, nullptr)); + tr_error_clear(&err); + + tr_sys_path_remove(path3, nullptr); + tr_sys_path_remove(path2, nullptr); + + tr_free(path3); + tr_free(path2); + tr_free(path1); +} + +TEST_F(FileTest, pathNativeSeparators) +{ + EXPECT_EQ(nullptr, tr_sys_path_native_separators(nullptr)); + + struct Test + { + std::string input; + std::string expected_output; + }; + + auto const tests = std::array + { + Test{ "", "" }, + { "a", TR_IF_WIN32("a", "a") }, + { "/", TR_IF_WIN32("\\", "/") }, + { "/a/b/c", TR_IF_WIN32("\\a\\b\\c", "/a/b/c") }, + { "C:\\a/b\\c", TR_IF_WIN32("C:\\a\\b\\c", "C:\\a/b\\c") }, + }; + + for (auto const& test : tests) + { + auto buf = std::string(test.input); + char* const output = tr_sys_path_native_separators(&buf.front()); + EXPECT_EQ(test.expected_output, output); + EXPECT_EQ(buf.data(), output); + } +} + +TEST_F(FileTest, fileOpen) +{ + auto const test_dir = createTestDir(currentTestName()); + + // can't open non-existent file + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + tr_error* err = nullptr; + EXPECT_TRUE(tr_sys_file_open(path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE); + EXPECT_NE(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + tr_error_clear(&err); + EXPECT_TRUE(tr_sys_file_open(path1, TR_SYS_FILE_WRITE, 0600, &err) == TR_BAD_SYS_FILE); + EXPECT_NE(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path1, nullptr)); + tr_error_clear(&err); + + // can't open directory + tr_sys_dir_create(path1, 0, 0777, nullptr); +#ifdef _WIN32 + // this works on *NIX + EXPECT_TRUE(tr_sys_file_open(path1, TR_SYS_FILE_READ, 0600, &err) == TR_BAD_SYS_FILE); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); +#endif + EXPECT_TRUE(tr_sys_file_open(path1, TR_SYS_FILE_WRITE, 0600, &err) == TR_BAD_SYS_FILE); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + tr_sys_path_remove(path1, nullptr); + + // can create non-existent file + auto fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0640, &err); + EXPECT_NE(TR_BAD_SYS_FILE, fd); + EXPECT_EQ(nullptr, err); + tr_sys_file_close(fd, nullptr); + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_TRUE(validatePermissions(path1, 0640)); + + // can open existing file + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0600, &err); + EXPECT_NE(TR_BAD_SYS_FILE, fd); + EXPECT_EQ(nullptr, err); + tr_sys_file_close(fd, nullptr); + fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE, 0600, &err); + EXPECT_NE(TR_BAD_SYS_FILE, fd); + EXPECT_EQ(nullptr, err); + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + createFileWithContents(path1, "test"); + + /* Can't create new file if it already exists */ + fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE_NEW, 0640, &err); + EXPECT_EQ(TR_BAD_SYS_FILE, fd); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + tr_sys_path_info info; + tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, nullptr); + EXPECT_EQ(4, info.size); + + /* Pointer is at the end of file */ + tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, nullptr); + EXPECT_EQ(4, info.size); + fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_APPEND, 0600, &err); + EXPECT_NE(TR_BAD_SYS_FILE, fd); + EXPECT_EQ(nullptr, err); + tr_sys_file_write(fd, "s", 1, nullptr, nullptr); /* On *NIX, pointer is positioned on each write but not initially */ + auto n = uint64_t {}; + tr_sys_file_seek(fd, 0, TR_SEEK_CUR, &n, nullptr); + EXPECT_EQ(5, n); + tr_sys_file_close(fd, nullptr); + + /* File gets truncated */ + tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, nullptr); + EXPECT_EQ(5, info.size); + fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_TRUNCATE, 0600, &err); + EXPECT_NE(TR_BAD_SYS_FILE, fd); + EXPECT_EQ(nullptr, err); + tr_sys_file_get_info(fd, &info, nullptr); + EXPECT_EQ(0, info.size); + tr_sys_file_close(fd, nullptr); + tr_sys_path_get_info(path1, TR_SYS_PATH_NO_FOLLOW, &info, nullptr); + EXPECT_EQ(0, info.size); + + /* TODO: symlink and hardlink tests */ + + tr_sys_path_remove(path1, nullptr); + + tr_free(path1); +} + +TEST_F(FileTest, fileReadWriteSeek) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto const fd = tr_sys_file_open(path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, nullptr); + + uint64_t n; + tr_error* err = nullptr; + EXPECT_TRUE(tr_sys_file_seek(fd, 0, TR_SEEK_CUR, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(0, n); + + EXPECT_TRUE(tr_sys_file_write(fd, "test", 4, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(4, n); + + EXPECT_TRUE(tr_sys_file_seek(fd, 0, TR_SEEK_CUR, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(4, n); + + EXPECT_TRUE(tr_sys_file_seek(fd, 0, TR_SEEK_SET, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(0, n); + + auto buf = std::array{}; + EXPECT_TRUE(tr_sys_file_read(fd, buf.data(), buf.size(), &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(4, n); + + EXPECT_EQ(0, memcmp("test", buf.data(), 4)); + + EXPECT_TRUE(tr_sys_file_seek(fd, -3, TR_SEEK_CUR, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(1, n); + + EXPECT_TRUE(tr_sys_file_write(fd, "E", 1, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(1, n); + + EXPECT_TRUE(tr_sys_file_seek(fd, -2, TR_SEEK_CUR, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(0, n); + + EXPECT_TRUE(tr_sys_file_read(fd, buf.data(), buf.size(), &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(4, n); + + EXPECT_EQ(0, memcmp("tEst", buf.data(), 4)); + + EXPECT_TRUE(tr_sys_file_seek(fd, 0, TR_SEEK_END, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(4, n); + + EXPECT_TRUE(tr_sys_file_write(fd, " ok", 3, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(3, n); + + EXPECT_TRUE(tr_sys_file_seek(fd, 0, TR_SEEK_SET, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(0, n); + + EXPECT_TRUE(tr_sys_file_read(fd, buf.data(), buf.size(), &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(7, n); + + EXPECT_EQ(0, memcmp("tEst ok", buf.data(), 7)); + + EXPECT_TRUE(tr_sys_file_write_at(fd, "-", 1, 4, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(1, n); + + EXPECT_TRUE(tr_sys_file_read_at(fd, buf.data(), 5, 2, &n, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(5, n); + + EXPECT_EQ(0, memcmp("st-ok", buf.data(), 5)); + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + + tr_free(path1); +} + +TEST_F(FileTest, fileTruncate) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.c_str(), "a", nullptr); + auto fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, nullptr); + + tr_error* err = nullptr; + EXPECT_TRUE(tr_sys_file_truncate(fd, 10, &err)); + EXPECT_EQ(nullptr, err); + tr_sys_path_info info; + tr_sys_file_get_info(fd, &info, nullptr); + EXPECT_EQ(10, info.size); + + EXPECT_TRUE(tr_sys_file_truncate(fd, 20, &err)); + EXPECT_EQ(nullptr, err); + tr_sys_file_get_info(fd, &info, nullptr); + EXPECT_EQ(20, info.size); + + EXPECT_TRUE(tr_sys_file_truncate(fd, 0, &err)); + EXPECT_EQ(nullptr, err); + tr_sys_file_get_info(fd, &info, nullptr); + EXPECT_EQ(0, info.size); + + EXPECT_TRUE(tr_sys_file_truncate(fd, 50, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_get_info(path1, 0, &info, nullptr); + EXPECT_EQ(50, info.size); + + fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, nullptr); + + EXPECT_TRUE(tr_sys_file_truncate(fd, 25, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_get_info(path1, 0, &info, nullptr); + EXPECT_EQ(25, info.size); + + tr_sys_path_remove(path1, nullptr); + + tr_free(path1); +} + +TEST_F(FileTest, filePreallocate) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, nullptr); + + tr_error* err = nullptr; + auto prealloc_size = size_t{ 50 }; + if (tr_sys_file_preallocate(fd, prealloc_size, 0, &err)) + { + EXPECT_EQ(nullptr, err); + tr_sys_path_info info; + tr_sys_file_get_info(fd, &info, nullptr); + EXPECT_EQ(prealloc_size, info.size); + } + else + { + EXPECT_NE(nullptr, err); + fprintf(stderr, "WARNING: [%s] unable to preallocate file (full): %s (%d)\n", __FUNCTION__, err->message, err->code); + tr_error_clear(&err); + } + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + + fd = tr_sys_file_open(path1, TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE, 0600, nullptr); + + prealloc_size = 500 * 1024 * 1024; + if (tr_sys_file_preallocate(fd, prealloc_size, TR_SYS_FILE_PREALLOC_SPARSE, &err)) + { + EXPECT_EQ(nullptr, err); + tr_sys_path_info info; + tr_sys_file_get_info(fd, &info, nullptr); + EXPECT_EQ(prealloc_size, info.size); + } + else + { + EXPECT_NE(nullptr, err); + fprintf(stderr, "WARNING: [%s] unable to preallocate file (sparse): %s (%d)\n", __FUNCTION__, err->message, err->code); + tr_error_clear(&err); + } + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + + tr_free(path1); +} + +TEST_F(FileTest, map) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto const contents = std::string { "test" }; + createFileWithContents(path1, contents.data()); + + auto fd = tr_sys_file_open(path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE, 0600, nullptr); + + tr_error* err = nullptr; + auto map_len = contents.size(); + auto* view = static_cast(tr_sys_file_map_for_reading(fd, 0, map_len, &err)); + EXPECT_NE(nullptr, view); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(contents, std::string(view, map_len)); + +#ifdef HAVE_UNIFIED_BUFFER_CACHE + + auto const contents_2 = std::string { "more" }; + auto n_written = uint64_t {}; + tr_sys_file_write_at(fd, contents_2.data(), contents_2.size(), 0, &n_written, &err); + EXPECT_EQ(map_len, contents_2.size()); + EXPECT_EQ(map_len, n_written); + EXPECT_EQ(nullptr, err); + EXPECT_EQ(contents_2, std::string(view, map_len)); + +#endif + + EXPECT_TRUE(tr_sys_file_unmap(view, map_len, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + + tr_free(path1); +} + +TEST_F(FileTest, fileUtilities) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto const contents = std::string { "a\nbc\r\ndef\nghij\r\n\n\nklmno\r" }; + createFileWithContents(path1, contents.data()); + + auto fd = tr_sys_file_open(path1, TR_SYS_FILE_READ, 0, nullptr); + + tr_error* err = nullptr; + auto buffer = std::array{}; + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("a", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("bc", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("def", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("ghij", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), 4, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("klmn", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("o", buffer.data()); + EXPECT_FALSE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("o", buffer.data()); // on EOF, buffer stays unchanged + + tr_sys_file_close(fd, nullptr); + + fd = tr_sys_file_open(path1, TR_SYS_FILE_READ | TR_SYS_FILE_WRITE | TR_SYS_FILE_TRUNCATE, 0, nullptr); + + EXPECT_TRUE(tr_sys_file_write_line(fd, "p", &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_file_write_line(fd, "", &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_file_write_line(fd, "qr", &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_file_write_fmt(fd, "s%cu\r\n", &err, 't')); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_file_write_line(fd, "", &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_file_write_line(fd, "", &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_file_write_fmt(fd, "v%sy%d", &err, "wx", 2)); + EXPECT_EQ(nullptr, err); + + tr_sys_file_seek(fd, 0, TR_SEEK_SET, nullptr, nullptr); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("p", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("qr", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("stu", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("", buffer.data()); + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("vwxy2", buffer.data()); + EXPECT_FALSE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), &err)); + EXPECT_EQ(nullptr, err); + EXPECT_STREQ("vwxy2", buffer.data()); // on EOF, buffer stays unchanged + + tr_sys_file_close(fd, nullptr); + + tr_sys_path_remove(path1, nullptr); + + tr_free(path1); +} + +TEST_F(FileTest, dirCreate) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(path1, "b", nullptr); + + // Can create directory which has parent + tr_error* err = nullptr; + EXPECT_TRUE(tr_sys_dir_create(path1, 0, 0700, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_TRUE(validatePermissions(path1, 0700)); + + tr_sys_path_remove(path1, nullptr); + createFileWithContents(path1, "test"); + + // Can't create directory where file already exists + EXPECT_FALSE(tr_sys_dir_create(path1, 0, 0700, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + EXPECT_FALSE(tr_sys_dir_create(path1, TR_SYS_DIR_CREATE_PARENTS, 0700, &err)); + EXPECT_NE(nullptr, err); + tr_error_clear(&err); + + tr_sys_path_remove(path1, nullptr); + + // Can't create directory which has no parent + EXPECT_FALSE(tr_sys_dir_create(path2, 0, 0700, &err)); + EXPECT_NE(nullptr, err); + EXPECT_FALSE(tr_sys_path_exists(path2, nullptr)); + tr_error_clear(&err); + + // Can create directory with parent directories + EXPECT_TRUE(tr_sys_dir_create(path2, TR_SYS_DIR_CREATE_PARENTS, 0751, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_path_exists(path1, nullptr)); + EXPECT_TRUE(tr_sys_path_exists(path2, nullptr)); + EXPECT_TRUE(validatePermissions(path1, 0751)); + EXPECT_TRUE(validatePermissions(path2, 0751)); + + // Can create existing directory (no-op) + EXPECT_TRUE(tr_sys_dir_create(path1, 0, 0700, &err)); + EXPECT_EQ(nullptr, err); + EXPECT_TRUE(tr_sys_dir_create(path1, TR_SYS_DIR_CREATE_PARENTS, 0700, &err)); + EXPECT_EQ(nullptr, err); + + tr_sys_path_remove(path2, nullptr); + tr_sys_path_remove(path1, nullptr); + + tr_free(path2); + tr_free(path1); +} + +TEST_F(FileTest, dirRead) +{ + auto const test_dir = createTestDir(currentTestName()); + + auto* path1 = tr_buildPath(test_dir.data(), "a", nullptr); + auto* path2 = tr_buildPath(test_dir.data(), "b", nullptr); + + bool have1; + bool have2; + testDirReadImpl(test_dir, &have1, &have2); + EXPECT_FALSE(have1); + EXPECT_FALSE(have2); + + createFileWithContents(path1, "test"); + testDirReadImpl(test_dir, &have1, &have2); + EXPECT_TRUE(have1); + EXPECT_FALSE(have2); + + createFileWithContents(path2, "test"); + testDirReadImpl(test_dir, &have1, &have2); + EXPECT_TRUE(have1); + EXPECT_TRUE(have2); + + tr_sys_path_remove(path1, nullptr); + testDirReadImpl(test_dir, &have1, &have2); + EXPECT_FALSE(have1); + EXPECT_TRUE(have2); + + tr_free(path2); + tr_free(path1); +} + +} // namespace test + +} // namespace libtransmission diff --git a/tests/libtransmission/getopt-test.cc b/tests/libtransmission/getopt-test.cc new file mode 100644 index 000000000..da91b7c17 --- /dev/null +++ b/tests/libtransmission/getopt-test.cc @@ -0,0 +1,154 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "tr-getopt.h" + +#include "gtest/gtest.h" + +#include + +namespace +{ + +auto constexpr Options = std::array{ + tr_option{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, nullptr }, + { 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "" }, + { 's', "piecesize", "Set how many KiB each piece should be, overriding the preferred default", "s", true, "" }, + { 'c', "comment", "Add a comment", "c", true, "" }, + { 't', "tracker", "Add a tracker's announce URL", "t", true, "" }, + { 'q', "pooka", "Pooka", "pk", false, nullptr }, + { 'V', "version", "Show version number and exit", "V", false, nullptr }, + { 0, nullptr, nullptr, nullptr, false, nullptr } +}; + +} // anonymous namespace + +class GetoptTest : public ::testing::Test +{ +protected: + void runTest(int argc, char const* const* argv, int expected_n, int const* expected_c, + char const* const* expected_args) const + { + auto n = int{}; + tr_optind = 1; + + int c; + char const* argstr; + while ((c = tr_getopt("summary", argc, argv, Options.data(), &argstr)) != TR_OPT_DONE) + { + EXPECT_LT(n, expected_n); + EXPECT_EQ(expected_c[n], c); + EXPECT_STREQ(expected_args[n], argstr); + ++n; + } + + EXPECT_EQ(expected_n, n); + } +}; + +TEST_F(GetoptTest, noOptions) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test" }; + auto constexpr ExpectedN = 0; + auto constexpr ExpectedC = std::array{}; + auto constexpr ExpectedOptArg = std::array{}; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, shortNoarg) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "-p" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'p' }; + auto constexpr ExpectedOptArg = std::array{ nullptr }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, longNoarg) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "--private" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'p' }; + auto constexpr ExpectedOptArg = std::array{ nullptr }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, shortWithArg) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "-o", "/tmp/outfile" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'o' }; + auto constexpr ExpectedOptArg = std::array{ "/tmp/outfile" }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, longWithArg) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "--outfile", "/tmp/outfile" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'o' }; + auto constexpr ExpectedOptArg = std::array{ "/tmp/outfile" }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, shortWithArgAfterEq) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "-o=/tmp/outfile" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'o' }; + auto constexpr ExpectedOptArg = std::array{ "/tmp/outfile" }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, longWithArgAfterEq) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "--outfile=/tmp/outfile" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'o' }; + auto constexpr ExpectedOptArg = std::array{ "/tmp/outfile" }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, unknownOption) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "-z" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ TR_OPT_UNK }; + auto constexpr ExpectedOptArg = std::array{ "-z" }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, missingArg) +{ + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "-o" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ TR_OPT_ERR }; + auto constexpr ExpectedOptArg = std::array{ nullptr }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, lotsOfOptions) +{ + auto constexpr Args = + std::array{ "/some/path/tr-getopt-test", "--piecesize=4", "-c", "hello world", "-p", "--tracker=foo" }; + auto constexpr ExpectedN = 4; + auto constexpr ExpectedC = std::array{ 's', 'c', 'p', 't' }; + auto constexpr ExpectedOptArg = std::array{ "4", "hello world", nullptr, "foo" }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} + +TEST_F(GetoptTest, matchLongerKey) +{ + // confirm that this resolves to 'q' and not 'p' + auto constexpr Args = std::array{ "/some/path/tr-getopt-test", "-pk" }; + auto constexpr ExpectedN = 1; + auto constexpr ExpectedC = std::array{ 'q' }; + auto constexpr ExpectedOptArg = std::array{ nullptr }; + runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data()); +} diff --git a/tests/libtransmission/history-test.cc b/tests/libtransmission/history-test.cc new file mode 100644 index 000000000..314c555f9 --- /dev/null +++ b/tests/libtransmission/history-test.cc @@ -0,0 +1,27 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "history.h" + +#include "gtest/gtest.h" + +TEST(History, recentHistory) +{ + auto h = tr_recentHistory {}; + + tr_historyAdd(&h, 10000, 1); + EXPECT_EQ(0, tr_historyGet(&h, 12000, 1000)); + EXPECT_EQ(1, tr_historyGet(&h, 12000, 3000)); + EXPECT_EQ(1, tr_historyGet(&h, 12000, 5000)); + tr_historyAdd(&h, 20000, 1); + EXPECT_EQ(0, tr_historyGet(&h, 22000, 1000)); + EXPECT_EQ(1, tr_historyGet(&h, 22000, 3000)); + EXPECT_EQ(2, tr_historyGet(&h, 22000, 15000)); + EXPECT_EQ(2, tr_historyGet(&h, 22000, 20000)); +} diff --git a/tests/libtransmission/json-test.cc b/tests/libtransmission/json-test.cc new file mode 100644 index 000000000..805f2720d --- /dev/null +++ b/tests/libtransmission/json-test.cc @@ -0,0 +1,255 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#define LIBTRANSMISSION_VARIANT_MODULE + +#include "transmission.h" +#include "utils.h" // tr_free() +#include "variant.h" +#include "variant-common.h" + +#include "gtest/gtest.h" + +#include // setlocale() +#include // strlen() +#include + +class JSONTest : public ::testing::TestWithParam +{ +protected: + void SetUp() override + { + auto const* locale_str = GetParam(); + if (setlocale(LC_NUMERIC, locale_str) == nullptr) + { + GTEST_SKIP(); + } + } +}; + +TEST_P(JSONTest, testElements) +{ + auto const in = std::string { + "{ \"string\": \"hello world\"," + " \"escaped\": \"bell \\b formfeed \\f linefeed \\n carriage return \\r tab \\t\"," + " \"int\": 5, " + " \"float\": 6.5, " + " \"true\": true, " + " \"false\": false, " + " \"null\": null }" + }; + + tr_variant top; + int err = tr_variantFromJson(&top, in.data(), in.size()); + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantIsDict(&top)); + + char const* str = {}; + auto key = tr_quark_new("string", 6); + EXPECT_TRUE(tr_variantDictFindStr(&top, key, &str, nullptr)); + EXPECT_STREQ("hello world", str); + + EXPECT_TRUE(tr_variantDictFindStr(&top, tr_quark_new("escaped", 7), &str, nullptr)); + EXPECT_STREQ("bell \b formfeed \f linefeed \n carriage return \r tab \t", str); + + auto i = int64_t {}; + EXPECT_TRUE(tr_variantDictFindInt(&top, tr_quark_new("int", 3), &i)); + EXPECT_EQ(5, i); + + auto d = double{}; + EXPECT_TRUE(tr_variantDictFindReal(&top, tr_quark_new("float", 5), &d)); + EXPECT_EQ(65, int(d * 10)); + + auto f = bool{}; + EXPECT_TRUE(tr_variantDictFindBool(&top, tr_quark_new("true", 4), &f)); + EXPECT_TRUE(f); + + EXPECT_TRUE(tr_variantDictFindBool(&top, tr_quark_new("false", 5), &f)); + EXPECT_FALSE(f); + + EXPECT_TRUE(tr_variantDictFindStr(&top, tr_quark_new("null", 4), &str, nullptr)); + EXPECT_STREQ("", str); + + if (err == 0) + { + tr_variantFree(&top); + } +} + +TEST_P(JSONTest, testUtf8) +{ + auto in = std::string { "{ \"key\": \"Letöltések\" }" }; + tr_variant top; + char const* str; + char* json; + int err; + tr_quark const key = tr_quark_new("key", 3); + + err = tr_variantFromJson(&top, in.data(), in.size()); + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantIsDict(&top)); + EXPECT_TRUE(tr_variantDictFindStr(&top, key, &str, nullptr)); + EXPECT_STREQ("Letöltések", str); + + if (err == 0) + { + tr_variantFree(&top); + } + + in = std::string { R"({ "key": "\u005C" })" }; + err = tr_variantFromJson(&top, in.data(), in.size()); + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantIsDict(&top)); + EXPECT_TRUE(tr_variantDictFindStr(&top, key, &str, nullptr)); + EXPECT_STREQ("\\", str); + + if (err == 0) + { + tr_variantFree(&top); + } + + /** + * 1. Feed it JSON-escaped nonascii to the JSON decoder. + * 2. Confirm that the result is UTF-8. + * 3. Feed the same UTF-8 back into the JSON encoder. + * 4. Confirm that the result is JSON-escaped. + * 5. Dogfood that result back into the parser. + * 6. Confirm that the result is UTF-8. + */ + in = std::string { R"({ "key": "Let\u00f6lt\u00e9sek" })" }; + err = tr_variantFromJson(&top, in.data(), in.size()); + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantIsDict(&top)); + EXPECT_TRUE(tr_variantDictFindStr(&top, key, &str, nullptr)); + EXPECT_STREQ("Letöltések", str); + json = tr_variantToStr(&top, TR_VARIANT_FMT_JSON, nullptr); + + if (err == 0) + { + tr_variantFree(&top); + } + + EXPECT_NE(nullptr, json); + EXPECT_NE(nullptr, strstr(json, "\\u00f6")); + EXPECT_NE(nullptr, strstr(json, "\\u00e9")); + err = tr_variantFromJson(&top, json, strlen(json)); + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantIsDict(&top)); + EXPECT_TRUE(tr_variantDictFindStr(&top, key, &str, nullptr)); + EXPECT_STREQ("Letöltések", str); + + if (err == 0) + { + tr_variantFree(&top); + } + + tr_free(json); +} + +TEST_P(JSONTest, test1) +{ + auto const in = std::string { + "{\n" + " \"headers\": {\n" + " \"type\": \"request\",\n" + " \"tag\": 666\n" + " },\n" + " \"body\": {\n" + " \"name\": \"torrent-info\",\n" + " \"arguments\": {\n" + " \"ids\": [ 7, 10 ]\n" + " }\n" + " }\n" + "}\n" + }; + + tr_variant top; + auto const err = tr_variantFromJson(&top, in.data(), in.size()); + + char const* str; + int64_t i; + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantIsDict(&top)); + auto* headers = tr_variantDictFind(&top, tr_quark_new("headers", 7)); + EXPECT_NE(nullptr, headers); + EXPECT_TRUE(tr_variantIsDict(headers)); + EXPECT_TRUE(tr_variantDictFindStr(headers, tr_quark_new("type", 4), &str, nullptr)); + EXPECT_STREQ("request", str); + EXPECT_TRUE(tr_variantDictFindInt(headers, TR_KEY_tag, &i)); + EXPECT_EQ(666, i); + auto* body = tr_variantDictFind(&top, tr_quark_new("body", 4)); + EXPECT_NE(nullptr, body); + EXPECT_TRUE(tr_variantDictFindStr(body, TR_KEY_name, &str, nullptr)); + EXPECT_STREQ("torrent-info", str); + auto* args = tr_variantDictFind(body, tr_quark_new("arguments", 9)); + EXPECT_NE(nullptr, args); + EXPECT_TRUE(tr_variantIsDict(args)); + auto* ids = tr_variantDictFind(args, TR_KEY_ids); + EXPECT_NE(nullptr, ids); + EXPECT_TRUE(tr_variantIsList(ids)); + EXPECT_EQ(2, tr_variantListSize(ids)); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(ids, 0), &i)); + EXPECT_EQ(7, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(ids, 1), &i)); + EXPECT_EQ(10, i); + + tr_variantFree(&top); +} + +TEST_P(JSONTest, test2) +{ + tr_variant top; + auto const in = std::string { " " }; + + top.type = 0; + int err = tr_variantFromJson(&top, in.data(), in.size()); + + EXPECT_NE(0, err); + EXPECT_FALSE(tr_variantIsDict(&top)); +} + +TEST_P(JSONTest, test3) +{ + auto const in = std::string { + "{ \"error\": 2," + " \"errorString\": \"torrent not registered with this tracker 6UHsVW'*C\"," + " \"eta\": 262792," + " \"id\": 25," + " \"leftUntilDone\": 2275655680 }" + }; + + tr_variant top; + auto const err = tr_variantFromJson(&top, in.data(), in.size()); + EXPECT_EQ(0, err); + + char const* str; + EXPECT_TRUE(tr_variantDictFindStr(&top, TR_KEY_errorString, &str, nullptr)); + EXPECT_STREQ("torrent not registered with this tracker 6UHsVW'*C", str); + + tr_variantFree(&top); +} + +TEST_P(JSONTest, unescape) +{ + tr_variant top; + auto const in = std::string { R"({ "string-1": "\/usr\/lib" })" }; + int const err = tr_variantFromJson(&top, in.data(), in.size()); + EXPECT_EQ(0, err); + + char const* str; + EXPECT_TRUE(tr_variantDictFindStr(&top, tr_quark_new("string-1", 8), &str, nullptr)); + EXPECT_STREQ("/usr/lib", str); + + tr_variantFree(&top); +} + +INSTANTIATE_TEST_SUITE_P( + JSON, + JSONTest, + ::testing::Values("C", "da_DK.UTF-8", "fr_FR.UTF-8", "ru_RU.UTF-8") + ); diff --git a/tests/libtransmission/magnet-test.cc b/tests/libtransmission/magnet-test.cc new file mode 100644 index 000000000..2b518cb83 --- /dev/null +++ b/tests/libtransmission/magnet-test.cc @@ -0,0 +1,54 @@ +/* + * This file Copyright (C) 2010-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "magnet.h" +#include "utils.h" + +#include "gtest/gtest.h" + +#include + +TEST(Magnet, magnetParse) +{ + auto constexpr ExpectedHash = std::array{ + 210, 53, 64, 16, 163, 202, 74, 222, 91, 116, + 39, 187, 9, 58, 98, 163, 137, 159, 243, 129 + }; + + char const* const uri_hex = + "magnet:?xt=urn:btih:" + "d2354010a3ca4ade5b7427bb093a62a3899ff381" + "&dn=Display%20Name" + "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" + "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce" + "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"; + + char const* const uri_base32 = + "magnet:?xt=urn:btih:" + "2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B" + "&dn=Display%20Name" + "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" + "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile" + "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"; + + for (auto const& uri : { uri_hex, uri_base32 }) + { + auto* info = tr_magnetParse(uri); + EXPECT_NE(nullptr, info); + EXPECT_EQ(2, info->trackerCount); + EXPECT_STREQ("http://tracker.openbittorrent.com/announce", info->trackers[0]); + EXPECT_STREQ("http://tracker.opentracker.org/announce", info->trackers[1]); + EXPECT_EQ(1, info->webseedCount); + EXPECT_STREQ("http://server.webseed.org/path/to/file", info->webseeds[0]); + EXPECT_STREQ("Display Name", info->displayName); + EXPECT_EQ(ExpectedHash.size(), sizeof(info->hash)); + EXPECT_EQ(0, memcmp(info->hash, ExpectedHash.data(), ExpectedHash.size())); + tr_magnetFree(info); + } +} diff --git a/tests/libtransmission/makemeta-test.cc b/tests/libtransmission/makemeta-test.cc new file mode 100644 index 000000000..ee9bf4d0f --- /dev/null +++ b/tests/libtransmission/makemeta-test.cc @@ -0,0 +1,246 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "crypto-utils.h" +#include "file.h" +#include "makemeta.h" +#include "utils.h" // tr_free() + +#include "test-fixtures.h" + +#include +#include // mktemp() +#include // strlen() +#include + +namespace libtransmission +{ + +namespace test +{ + +class MakemetaTest : public SandboxedTest +{ +protected: + void testSingleFileImpl(tr_tracker_info const* trackers, int const trackerCount, void const* payload, + size_t const payloadSize, char const* comment, bool isPrivate) + { + // char* sandbox; + tr_info inf; + + // create a single input file + auto input_file = makeString(tr_buildPath(sandboxDir().data(), "test.XXXXXX", nullptr)); + createTmpfileWithContents(input_file, payload, payloadSize); + tr_sys_path_native_separators(&input_file.front()); + auto* builder = tr_metaInfoBuilderCreate(input_file.c_str()); + EXPECT_EQ(tr_file_index_t{ 1 }, builder->fileCount); + EXPECT_STREQ(input_file.c_str(), builder->top); + EXPECT_STREQ(input_file.c_str(), builder->files[0].filename); + EXPECT_EQ(payloadSize, builder->files[0].size); + EXPECT_EQ(payloadSize, builder->totalSize); + EXPECT_FALSE(builder->isFolder); + EXPECT_FALSE(builder->abortFlag); + + // have tr_makeMetaInfo() build the .torrent file + auto* torrent_file = tr_strdup_printf("%s.torrent", input_file.data()); + tr_makeMetaInfo(builder, torrent_file, trackers, trackerCount, comment, isPrivate); + EXPECT_EQ(isPrivate, builder->isPrivate); + EXPECT_STREQ(torrent_file, builder->outputFile); + EXPECT_STREQ(comment, builder->comment); + EXPECT_EQ(trackerCount, builder->trackerCount); + + while (!builder->isDone) + { + tr_wait_msec(100); + } + + // now let's check our work: parse the .torrent file + auto* ctor = tr_ctorNew(nullptr); + sync(); + tr_ctorSetMetainfoFromFile(ctor, torrent_file); + auto const parse_result = tr_torrentParse(ctor, &inf); + EXPECT_EQ(TR_PARSE_OK, parse_result); + + // quick check of some of the parsed metainfo + EXPECT_EQ(payloadSize, inf.totalSize); + EXPECT_EQ(makeString(tr_sys_path_basename(input_file.data(), nullptr)), inf.name); + EXPECT_STREQ(comment, inf.comment); + EXPECT_EQ(tr_file_index_t{ 1 }, inf.fileCount); + EXPECT_EQ(isPrivate, inf.isPrivate); + EXPECT_FALSE(inf.isFolder); + EXPECT_EQ(trackerCount, inf.trackerCount); + + // cleanup + tr_free(torrent_file); + tr_ctorFree(ctor); + tr_metainfoFree(&inf); + tr_metaInfoBuilderFree(builder); + } + + void testSingleDirectoryImpl(tr_tracker_info const* trackers, int const tracker_count, void const** payloads, + size_t const* payload_sizes, size_t const payload_count, char const* comment, + bool const is_private) + { + // create the top temp directory + auto* top = tr_buildPath(sandboxDir().data(), "folder.XXXXXX", nullptr); + tr_sys_path_native_separators(top); + tr_sys_dir_create_temp(top, nullptr); + + // build the payload files that go into the top temp directory + auto files = std::vector{}; + files.reserve(payload_count); + size_t total_size = 0; + + for (size_t i = 0; i < payload_count; i++) + { + auto tmpl = std::array{}; + tr_snprintf(tmpl.data(), tmpl.size(), "file.%04zu%s", i, "XXXXXX"); + auto path = makeString(tr_buildPath(top, tmpl.data(), nullptr)); + createTmpfileWithContents(path, payloads[i], payload_sizes[i]); + tr_sys_path_native_separators(&path.front()); + files.push_back(path); + total_size += payload_sizes[i]; + } + + sync(); + + // init the builder + auto* builder = tr_metaInfoBuilderCreate(top); + EXPECT_FALSE(builder->abortFlag); + EXPECT_STREQ(top, builder->top); + EXPECT_EQ(payload_count, builder->fileCount); + EXPECT_EQ(total_size, builder->totalSize); + EXPECT_TRUE(builder->isFolder); + + for (size_t i = 0; i < builder->fileCount; ++i) + { + EXPECT_EQ(files[i], builder->files[i].filename); + EXPECT_EQ(payload_sizes[i], builder->files[i].size); + } + + // build the .torrent file + auto* torrent_file = tr_strdup_printf("%s.torrent", top); + tr_makeMetaInfo(builder, torrent_file, trackers, tracker_count, comment, is_private); + EXPECT_EQ(is_private, builder->isPrivate); + EXPECT_STREQ(torrent_file, builder->outputFile); + EXPECT_STREQ(comment, builder->comment); + EXPECT_EQ(tracker_count, builder->trackerCount); + auto test = [&builder]() { return builder->isDone; }; + EXPECT_TRUE(waitFor(test, 5000)); + sync(); + + // now let's check our work: parse the .torrent file + auto* ctor = tr_ctorNew(nullptr); + tr_ctorSetMetainfoFromFile(ctor, torrent_file); + tr_info inf; + auto parse_result = tr_torrentParse(ctor, &inf); + EXPECT_EQ(TR_PARSE_OK, parse_result); + + // quick check of some of the parsed metainfo + EXPECT_EQ(total_size, inf.totalSize); + auto* tmpstr = tr_sys_path_basename(top, nullptr); + EXPECT_STREQ(tmpstr, inf.name); + tr_free(tmpstr); + EXPECT_STREQ(comment, inf.comment); + EXPECT_EQ(payload_count, inf.fileCount); + EXPECT_EQ(is_private, inf.isPrivate); + EXPECT_EQ(builder->isFolder, inf.isFolder); + EXPECT_EQ(tracker_count, inf.trackerCount); + + // cleanup + tr_free(torrent_file); + tr_ctorFree(ctor); + tr_metainfoFree(&inf); + tr_metaInfoBuilderFree(builder); + + tr_free(top); + } + + void testSingleDirectoryRandomPayloadImpl(tr_tracker_info const* trackers, int const tracker_count, + size_t const max_file_count, size_t const max_file_size, char const* comment, + bool const is_private) + { + // build random payloads + size_t payload_count = 1 + tr_rand_int_weak(max_file_count); + void** payloads = tr_new0(void*, payload_count); + size_t* payload_sizes = tr_new0(size_t, payload_count); + + for (size_t i = 0; i < payload_count; i++) + { + size_t const n = 1 + tr_rand_int_weak(max_file_size); + payloads[i] = tr_new(char, n); + tr_rand_buffer(payloads[i], n); + payload_sizes[i] = n; + } + + // run the test + testSingleDirectoryImpl( + trackers, tracker_count, + const_cast(payloads), + payload_sizes, + payload_count, + comment, is_private); + + // cleanup + for (size_t i = 0; i < payload_count; i++) + { + tr_free(payloads[i]); + } + + tr_free(payloads); + tr_free(payload_sizes); + } +}; + +TEST_F(MakemetaTest, singleFile) +{ + auto trackers = std::array{}; + auto tracker_count = int{}; + trackers[tracker_count].tier = tracker_count; + trackers[tracker_count].announce = const_cast("udp://tracker.openbittorrent.com:80"); + ++tracker_count; + trackers[tracker_count].tier = tracker_count; + trackers[tracker_count].announce = const_cast("udp://tracker.publicbt.com:80"); + ++tracker_count; + auto const payload = std::string { "Hello, World!\n" }; + char const* const comment = "This is the comment"; + bool const is_private = false; + testSingleFileImpl(trackers.data(), tracker_count, + payload.data(), payload.size(), + comment, is_private); +} + +TEST_F(MakemetaTest, singleDirectoryRandomPayload) +{ + auto constexpr DefaultMaxFileCount = size_t{ 16 }; + auto constexpr DefaultMaxFileSize = size_t{ 1024 }; + + auto trackers = std::array{}; + auto tracker_count = int{}; + trackers[tracker_count].tier = tracker_count; + trackers[tracker_count].announce = const_cast("udp://tracker.openbittorrent.com:80"); + ++tracker_count; + trackers[tracker_count].tier = tracker_count; + trackers[tracker_count].announce = const_cast("udp://tracker.publicbt.com:80"); + ++tracker_count; + char const* const comment = "This is the comment"; + bool const is_private = false; + + for (size_t i = 0; i < 10; ++i) + { + testSingleDirectoryRandomPayloadImpl(trackers.data(), tracker_count, + DefaultMaxFileCount, + DefaultMaxFileSize, + comment, is_private); + } +} + +} // namespace test + +} // namespace libtransmission diff --git a/libtransmission/metainfo-test.c b/tests/libtransmission/metainfo-test.cc similarity index 57% rename from libtransmission/metainfo-test.c rename to tests/libtransmission/metainfo-test.cc index 64e93de98..ff13f1ae5 100644 --- a/libtransmission/metainfo-test.c +++ b/tests/libtransmission/metainfo-test.cc @@ -6,44 +6,42 @@ * */ -#include "libtransmission-test.h" - #include "transmission.h" #include "metainfo.h" #include "utils.h" -#include +#include "gtest/gtest.h" -static int test_magnet_link(void) +#include +#include +#include + +TEST(Metainfo, magnetLink) { - tr_info inf; - tr_ctor* ctor; - char const* magnet_link; - tr_parse_result parse_result; - - /* background info @ http://wiki.theory.org/BitTorrent_Magnet-URI_Webseeding */ - magnet_link = + // background info @ http://wiki.theory.org/BitTorrent_Magnet-URI_Webseeding + char const constexpr* const MagnetLink = "magnet:?" "xt=urn:btih:14FFE5DD23188FD5CB53A1D47F1289DB70ABF31E" "&dn=ubuntu+12+04+1+desktop+32+bit" "&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce" "&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80" "&ws=http://transmissionbt.com "; - ctor = tr_ctorNew(NULL); - tr_ctorSetMetainfoFromMagnetLink(ctor, magnet_link); - parse_result = tr_torrentParse(ctor, &inf); - check_int(inf.fileCount, ==, 0); /* cos it's a magnet link */ - check_int(parse_result, ==, TR_PARSE_OK); - check_int(inf.trackerCount, ==, 2); - check_str(inf.trackers[0].announce, ==, "http://tracker.publicbt.com/announce"); - check_str(inf.trackers[1].announce, ==, "udp://tracker.publicbt.com:80"); - check_int(inf.webseedCount, ==, 1); - check_str(inf.webseeds[0], ==, "http://transmissionbt.com"); + + auto* ctor = tr_ctorNew(nullptr); + tr_ctorSetMetainfoFromMagnetLink(ctor, MagnetLink); + tr_info inf; + auto const parse_result = tr_torrentParse(ctor, &inf); + EXPECT_EQ(TR_PARSE_OK, parse_result); + EXPECT_EQ(0, inf.fileCount); // because it's a magnet link + EXPECT_EQ(2, inf.trackerCount); + EXPECT_STREQ("http://tracker.publicbt.com/announce", inf.trackers[0].announce); + EXPECT_STREQ("udp://tracker.publicbt.com:80", inf.trackers[1].announce); + EXPECT_EQ(1, inf.webseedCount); + EXPECT_STREQ("http://transmissionbt.com", inf.webseeds[0]); /* cleanup */ tr_metainfoFree(&inf); tr_ctorFree(ctor); - return 0; } #define BEFORE_PATH \ @@ -51,17 +49,18 @@ static int test_magnet_link(void) #define AFTER_PATH \ "eed6:lengthi2e4:pathl5:b.txteee4:name3:foo12:piece lengthi32768e6:pieces20:ÞÉ`âM‘‹Šs¡Å;˺¬.åÂà7:privatei0eee" -static int test_metainfo(void) +// FIXME: split these into parameterized tests? +TEST(Metainfo, bucket) { - struct + struct Test { int expected_benc_err; int expected_parse_result; void const* benc; - } - const metainfo[] = - { - { 0, TR_PARSE_OK, BEFORE_PATH "5:a.txt" AFTER_PATH }, + }; + + auto constexpr Tests = std::array{ + Test{ 0, TR_PARSE_OK, BEFORE_PATH "5:a.txt" AFTER_PATH }, /* allow empty components, but not =all= empty components, see bug #5517 */ { 0, TR_PARSE_OK, BEFORE_PATH "0:5:a.txt" AFTER_PATH }, @@ -82,65 +81,63 @@ static int test_metainfo(void) { EILSEQ, TR_PARSE_ERR, "" } }; - tr_logSetLevel(0); /* yes, we already know these will generate errors, thank you... */ + tr_logSetLevel(TR_LOG_SILENT); - for (size_t i = 0; i < TR_N_ELEMENTS(metainfo); i++) + for (auto const& test : Tests) { - tr_ctor* ctor = tr_ctorNew(NULL); - int const err = tr_ctorSetMetainfo(ctor, metainfo[i].benc, strlen(metainfo[i].benc)); - check_int(err, ==, metainfo[i].expected_benc_err); + auto* ctor = tr_ctorNew(nullptr); + int const err = tr_ctorSetMetainfo(ctor, test.benc, strlen(static_cast(test.benc))); + EXPECT_EQ(test.expected_benc_err, err); if (err == 0) { - tr_parse_result const parse_result = tr_torrentParse(ctor, NULL); - check_int(parse_result, ==, metainfo[i].expected_parse_result); + tr_parse_result const parse_result = tr_torrentParse(ctor, nullptr); + EXPECT_EQ(test.expected_parse_result, parse_result); } tr_ctorFree(ctor); } - - return 0; } -static int test_sanitize(void) +TEST(Metainfo, sanitize) { - struct + struct Test { char const* str; size_t len; char const* expected_result; bool expected_is_adjusted; - } - const test_data[] = - { - /* skipped */ - { "", 0, NULL, false }, - { ".", 1, NULL, false }, - { "..", 2, NULL, true }, - { ".....", 5, NULL, false }, - { " ", 2, NULL, false }, - { " . ", 3, NULL, false }, - { ". . .", 5, NULL, false }, - /* replaced with '_' */ + }; + + auto constexpr Tests = std::array{ + // skipped + Test{ "", 0, nullptr, false }, + { ".", 1, nullptr, false }, + { "..", 2, nullptr, true }, + { ".....", 5, nullptr, false }, + { " ", 2, nullptr, false }, + { " . ", 3, nullptr, false }, + { ". . .", 5, nullptr, false }, + // replaced with '_' { "/", 1, "_", true }, { "////", 4, "____", true }, { "\\\\", 2, "__", true }, { "/../", 4, "_.._", true }, { "foo + +#include "transmission.h" +#include "cache.h" // tr_cacheWriteBlock() +#include "file.h" // tr_sys_path_*() +#include "variant.h" + +#include "test-fixtures.h" + +#include +#include + +namespace libtransmission +{ + +namespace test +{ + +class IncompleteDirTest : + public SessionTest, + public ::testing::WithParamInterface> +{ +protected: + void SetUp() override + { + auto const incomplete_dir = GetParam().first; + auto const download_dir = GetParam().second; + tr_variantDictAddStr(settings(), TR_KEY_download_dir, download_dir.data()); + tr_variantDictAddStr(settings(), TR_KEY_incomplete_dir, incomplete_dir.data()); + tr_variantDictAddBool(settings(), TR_KEY_incomplete_dir_enabled, true); + + SessionTest::SetUp(); + } +}; + +TEST_P(IncompleteDirTest, incompleteDir) +{ + auto const* download_dir = tr_sessionGetDownloadDir(session_); + auto const* incomplete_dir = tr_sessionGetIncompleteDir(session_); + + // init an incomplete torrent. + // the test zero_torrent will be missing its first piece. + auto* tor = zeroTorrentInit(); + zeroTorrentPopulate(tor, false); + EXPECT_EQ(makeString(tr_strdup_printf("%s/%s.part", incomplete_dir, tor->info.files[0].name)), + makeString(tr_torrentFindFile(tor, 0))); + EXPECT_EQ(makeString(tr_buildPath(incomplete_dir, tor->info.files[1].name, nullptr)), + makeString(tr_torrentFindFile(tor, 1))); + EXPECT_EQ(tor->info.pieceSize, tr_torrentStat(tor)->leftUntilDone); + + // auto constexpr completeness_unset = tr_completeness { -1 }; + // auto completeness = completeness_unset; + int completeness = -1; + auto const zeroes_completeness_func = [] ( + tr_torrent* /*torrent*/, tr_completeness c, + bool /*was_running*/, void* vc) noexcept + { + *static_cast(vc) = c; + }; + tr_torrentSetCompletenessCallback(tor, zeroes_completeness_func, &completeness); + + struct TestIncompleteDirData + { + tr_session* session = {}; + tr_torrent* tor = {}; + tr_block_index_t block = {}; + tr_piece_index_t pieceIndex = {}; + uint32_t offset = {}; + struct evbuffer* buf = {}; + bool done = {}; + }; + + auto const test_incomplete_dir_threadfunc = [] (void* vdata)noexcept + { + auto* data = static_cast(vdata); + tr_cacheWriteBlock(data->session->cache, data->tor, 0, data->offset, data->tor->blockSize, data->buf); + tr_torrentGotBlock(data->tor, data->block); + data->done = true; + }; + + // now finish writing it + { + char* zero_block = tr_new0(char, tor->blockSize); + + struct TestIncompleteDirData data = {}; + data.session = session_; + data.tor = tor; + data.buf = evbuffer_new(); + + tr_block_index_t first; + tr_block_index_t last; + tr_torGetPieceBlockRange(tor, data.pieceIndex, &first, &last); + + for (tr_block_index_t block_index = first; block_index <= last; ++block_index) + { + evbuffer_add(data.buf, zero_block, tor->blockSize); + data.block = block_index; + data.done = false; + data.offset = data.block * tor->blockSize; + tr_runInEventThread(session_, test_incomplete_dir_threadfunc, &data); + + auto const test = [&data]() { return data.done; }; + EXPECT_TRUE(waitFor(test, 1000)); + } + + evbuffer_free(data.buf); + tr_free(zero_block); + } + + blockingTorrentVerify(tor); + EXPECT_EQ(0, tr_torrentStat(tor)->leftUntilDone); + + auto test = [&completeness]() { return completeness != -1; }; + EXPECT_TRUE(waitFor(test, 300)); + EXPECT_EQ(TR_SEED, completeness); + + for (tr_file_index_t file_index = 0; file_index < tor->info.fileCount; ++file_index) + { + EXPECT_EQ(makeString(tr_buildPath(download_dir, tor->info.files[file_index].name, nullptr)), + makeString(tr_torrentFindFile(tor, file_index))); + } + + // cleanup + tr_torrentRemove(tor, true, tr_sys_path_remove); +} + +INSTANTIATE_TEST_SUITE_P( + IncompleteDir, + IncompleteDirTest, + ::testing::Values( + // what happens when incompleteDir is a subdir of downloadDir + std::make_pair(std::string{ "Downloads/Incomplete" }, std::string{ "Downloads" }), + // test what happens when downloadDir is a subdir of incompleteDir + std::make_pair(std::string{ "Downloads" }, std::string{ "Downloads/Complete" }), + // test what happens when downloadDir and incompleteDir are siblings + std::make_pair(std::string{ "Incomplete" }, std::string{ "Downloads" }) + ) + ); + +/*** +**** +***/ + +using MoveTest = SessionTest; + +TEST_F(MoveTest, setLocation) +{ + auto const target_dir = makeString(tr_buildPath(tr_sessionGetConfigDir(session_), "target", nullptr)); + tr_sys_dir_create(target_dir.data(), TR_SYS_DIR_CREATE_PARENTS, 0777, nullptr); + + // init a torrent. + auto* tor = zeroTorrentInit(); + zeroTorrentPopulate(tor, true); + blockingTorrentVerify(tor); + EXPECT_EQ(0, tr_torrentStat(tor)->leftUntilDone); + + // now move it + auto state = int{ -1 }; + tr_torrentSetLocation(tor, target_dir.data(), true, nullptr, &state); + auto test = [&state]() { return state == TR_LOC_DONE; }; + EXPECT_TRUE(waitFor(test, 300)); + EXPECT_EQ(TR_LOC_DONE, state); + + // confirm the torrent is still complete after being moved + blockingTorrentVerify(tor); + EXPECT_EQ(0, tr_torrentStat(tor)->leftUntilDone); + + // confirm the files really got moved + sync(); + for (tr_file_index_t file_index = 0; file_index < tor->info.fileCount; ++file_index) + { + EXPECT_EQ(makeString(tr_buildPath(target_dir.data(), tor->info.files[file_index].name, nullptr)), + makeString(tr_torrentFindFile(tor, file_index))); + } + + // cleanup + tr_torrentRemove(tor, true, tr_sys_path_remove); +} + +} // namespace test + +} // namespace libtransmission diff --git a/libtransmission/peer-msgs-test.c b/tests/libtransmission/peer-msgs-test.cc similarity index 90% rename from libtransmission/peer-msgs-test.c rename to tests/libtransmission/peer-msgs-test.cc index d204f4239..ced856074 100644 --- a/libtransmission/peer-msgs-test.c +++ b/tests/libtransmission/peer-msgs-test.cc @@ -6,16 +6,13 @@ * */ -#include -#include - #include "transmission.h" #include "peer-msgs.h" #include "utils.h" -#include "libtransmission-test.h" +#include "gtest/gtest.h" -int main(void) +TEST(PeerMsgs, placeholder) { #if 0 @@ -42,6 +39,4 @@ int main(void) check_mem(buf, ==, pieces, numgot); #endif - - return 0; } diff --git a/tests/libtransmission/quark-test.cc b/tests/libtransmission/quark-test.cc new file mode 100644 index 000000000..d8a61d9f1 --- /dev/null +++ b/tests/libtransmission/quark-test.cc @@ -0,0 +1,57 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "quark.h" + +#include "gtest/gtest.h" + +#include +#include + +class QuarkTest : public ::testing::Test +{ +protected: + template + std::string quarkGetString(T i) + { + size_t len; + char const* const str = tr_quark_get_string(tr_quark(i), &len); + EXPECT_EQ(strlen(str), len); + return std::string(str, len); + } +}; + +TEST_F(QuarkTest, allPredefinedKeysCanBeLookedUp) +{ + for (int i = 0; i < TR_N_KEYS; i++) + { + auto const str = quarkGetString(i); + + tr_quark q; + EXPECT_TRUE(tr_quark_lookup(str.data(), str.size(), &q)); + EXPECT_EQ(i, q); + } +} + +TEST_F(QuarkTest, allPredefinedKeysAreSorted) +{ + for (int i = 0; i + 1 < TR_N_KEYS; i++) + { + auto const str1 = quarkGetString(i); + auto const str2 = quarkGetString(i + 1); + EXPECT_LT(str1, str2); + } +} + +TEST_F(QuarkTest, newEmptyQuarkReturnsNone) +{ + auto const q = tr_quark_new(nullptr, TR_BAD_SIZE); + EXPECT_EQ(TR_KEY_NONE, q); + EXPECT_EQ(std::string{ "" }, quarkGetString(q)); +} diff --git a/tests/libtransmission/rename-test.cc b/tests/libtransmission/rename-test.cc new file mode 100644 index 000000000..a8f2221aa --- /dev/null +++ b/tests/libtransmission/rename-test.cc @@ -0,0 +1,536 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "crypto-utils.h" +#include "file.h" +#include "resume.h" +#include "torrent.h" // tr_isTorrent() +#include "tr-assert.h" +#include "variant.h" + +#include "test-fixtures.h" + +#include +#include +#include // fopen() +#include // strcmp() +#include + +namespace libtransmission +{ + +namespace test +{ + +class RenameTest : public SessionTest +{ + static auto constexpr MaxWaitMsec = 3000; + +protected: + void torrentRemoveAndWait(tr_torrent* tor, int expected_torrent_count) + { + tr_torrentRemove(tor, false, nullptr); + auto const test = [this, expected_torrent_count]() + { + return tr_sessionCountTorrents(session_) == expected_torrent_count; + }; + EXPECT_TRUE(waitFor(test, MaxWaitMsec)); + } + + void createSingleFileTorrentContents(char const* top) + { + auto const path = makeString(tr_buildPath(top, "hello-world.txt", nullptr)); + createFileWithContents(path, "hello, world!\n"); + } + + void createMultifileTorrentContents(char const* top) + { + auto path = makeString(tr_buildPath(top, "Felidae", "Felinae", "Acinonyx", "Cheetah", "Chester", nullptr)); + createFileWithContents(path, "It ain't easy bein' cheesy.\n"); + + path = makeString(tr_buildPath(top, "Felidae", "Pantherinae", "Panthera", "Tiger", "Tony", nullptr)); + createFileWithContents(path, "They’re Grrrrreat!\n"); + + path = makeString(tr_buildPath(top, "Felidae", "Felinae", "Felis", "catus", "Kyphi", nullptr)); + createFileWithContents(path, "Inquisitive\n"); + + path = makeString(tr_buildPath(top, "Felidae", "Felinae", "Felis", "catus", "Saffron", nullptr)); + createFileWithContents(path, "Tough\n"); + + sync(); + } + + tr_torrent* createTorrentFromBase64Metainfo(tr_ctor* ctor, char const* metainfo_base64) + { + // create the torrent ctor + size_t metainfo_len; + auto* metainfo = static_cast(tr_base64_decode_str(metainfo_base64, &metainfo_len)); + EXPECT_NE(nullptr, metainfo); + EXPECT_LT(size_t(0), metainfo_len); + tr_ctorSetMetainfo(ctor, reinterpret_cast(metainfo), metainfo_len); + tr_ctorSetPaused(ctor, TR_FORCE, true); + + // create the torrent + auto err = int{}; + auto* tor = tr_torrentNew(ctor, &err, nullptr); + EXPECT_EQ(0, err); + + // cleanup + tr_free(metainfo); + return tor; + } + + bool testFileExistsAndConsistsOfThisString(tr_torrent const* tor, tr_file_index_t file_index, std::string const& str) + { + auto const str_len = str.size(); + auto success = false; + + auto* path = tr_torrentFindFile(tor, file_index); + if (path != nullptr) + { + EXPECT_TRUE(tr_sys_path_exists(path, nullptr)); + + size_t contents_len; + uint8_t* contents = tr_loadFile(path, &contents_len, nullptr); + + success = contents != nullptr && str_len == contents_len && memcmp(contents, str.data(), contents_len) == 0; + + tr_free(contents); + tr_free(path); + } + + return success; + } + + void expectHaveNone(tr_torrent* tor, uint64_t total_size) + { + auto const* tst = tr_torrentStat(tor); + EXPECT_EQ(TR_STATUS_STOPPED, tst->activity); + EXPECT_EQ(TR_STAT_OK, tst->error); + EXPECT_EQ(total_size, tst->sizeWhenDone); + EXPECT_EQ(total_size, tst->leftUntilDone); + EXPECT_EQ(total_size, tor->info.totalSize); + EXPECT_EQ(0, tst->haveValid); + } + + int torrentRenameAndWait(tr_torrent* tor, char const* oldpath, char const* newname) + { + auto const on_rename_done = [] ( + tr_torrent* /*tor*/, char const* /*oldpath*/, + char const* /*newname*/, int error, + void* user_data) noexcept + { + *static_cast(user_data) = error; + }; + + int error = -1; + tr_torrentRenamePath(tor, oldpath, newname, on_rename_done, &error); + auto test = [&error]() { return error != -1; }; + EXPECT_TRUE(waitFor(test, MaxWaitMsec)); + return error; + } +}; + +TEST_F(RenameTest, singleFilenameTorrent) +{ + uint64_t loaded; + static auto constexpr TotalSize = size_t{ 14 }; + + // this is a single-file torrent whose file is hello-world.txt, holding the string "hello, world!" + auto* ctor = tr_ctorNew(session_); + auto* tor = createTorrentFromBase64Metainfo(ctor, + "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" + "ZWkxMzU4NTQ5MDk4ZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDY6bGVuZ3RoaTE0ZTQ6bmFtZTE1" + "OmhlbGxvLXdvcmxkLnR4dDEyOnBpZWNlIGxlbmd0aGkzMjc2OGU2OnBpZWNlczIwOukboJcrkFUY" + "f6LvqLXBVvSHqCk6Nzpwcml2YXRlaTBlZWU="); + EXPECT_TRUE(tr_isTorrent(tor)); + + // sanity check the info + EXPECT_EQ(tr_file_index_t{ 1 }, tor->info.fileCount); + EXPECT_STREQ("hello-world.txt", tor->info.files[0].name); + EXPECT_FALSE(tor->info.files[0].is_renamed); + + // sanity check the (empty) stats + blockingTorrentVerify(tor); + expectHaveNone(tor, TotalSize); + + createSingleFileTorrentContents(tor->currentDir); + + // sanity check the stats again, now that we've added the file + blockingTorrentVerify(tor); + auto const* st = tr_torrentStat(tor); + EXPECT_EQ(TR_STATUS_STOPPED, st->activity); + EXPECT_EQ(TR_STAT_OK, st->error); + EXPECT_EQ(0, st->leftUntilDone); + EXPECT_EQ(0, st->haveUnchecked); + EXPECT_EQ(0, st->desiredAvailable); + EXPECT_EQ(TotalSize, st->sizeWhenDone); + EXPECT_EQ(TotalSize, st->haveValid); + + /** + *** okay! we've finally put together all the scaffolding to test + *** renaming a single-file torrent + **/ + + // confirm that bad inputs get caught + + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", nullptr)); + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", "")); + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", ".")); + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", "..")); + EXPECT_EQ(0, torrentRenameAndWait(tor, "hello-world.txt", "hello-world.txt")); + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "hello-world.txt", "hello/world.txt")); + + EXPECT_FALSE(tor->info.files[0].is_renamed); + EXPECT_STREQ("hello-world.txt", tor->info.files[0].name); + + /*** + **** Now try a rename that should succeed + ***/ + + auto tmpstr = makeString(tr_buildPath(tor->currentDir, "hello-world.txt", nullptr)); + EXPECT_TRUE(tr_sys_path_exists(tmpstr.c_str(), nullptr)); + EXPECT_STREQ("hello-world.txt", tr_torrentName(tor)); + EXPECT_EQ(0, torrentRenameAndWait(tor, tor->info.name, "foobar")); + EXPECT_FALSE(tr_sys_path_exists(tmpstr.c_str(), nullptr)); // confirm the old filename can't be found + EXPECT_TRUE(tor->info.files[0].is_renamed); // confirm the file's 'renamed' flag is set + EXPECT_STREQ("foobar", tr_torrentName(tor)); // confirm the torrent's name is now 'foobar' + EXPECT_STREQ("foobar", tor->info.files[0].name); // confirm the file's name is now 'foobar' in our struct + EXPECT_STREQ(nullptr, strstr(tor->info.torrent, "foobar")); // confirm the name in the .torrent file hasn't changed + tmpstr = makeString(tr_buildPath(tor->currentDir, "foobar", nullptr)); + EXPECT_TRUE(tr_sys_path_exists(tmpstr.c_str(), nullptr)); // confirm the file's name is now 'foobar' on the disk + EXPECT_TRUE(testFileExistsAndConsistsOfThisString(tor, 0, "hello, world!\n")); // confirm the contents are right + + // (while it's renamed: confirm that the .resume file remembers the changes) + tr_torrentSaveResume(tor); + sync(); + loaded = tr_torrentLoadResume(tor, ~0, ctor, nullptr); + EXPECT_STREQ("foobar", tr_torrentName(tor)); + EXPECT_NE(decltype(loaded) { 0 }, (loaded & TR_FR_NAME)); + + /*** + **** ...and rename it back again + ***/ + + tmpstr = makeString(tr_buildPath(tor->currentDir, "foobar", nullptr)); + EXPECT_TRUE(tr_sys_path_exists(tmpstr.c_str(), nullptr)); + EXPECT_EQ(0, torrentRenameAndWait(tor, "foobar", "hello-world.txt")); + EXPECT_FALSE(tr_sys_path_exists(tmpstr.c_str(), nullptr)); + EXPECT_TRUE(tor->info.files[0].is_renamed); + EXPECT_STREQ("hello-world.txt", tor->info.files[0].name); + EXPECT_STREQ("hello-world.txt", tr_torrentName(tor)); + EXPECT_TRUE(testFileExistsAndConsistsOfThisString(tor, 0, "hello, world!\n")); + + // cleanup + tr_ctorFree(ctor); + torrentRemoveAndWait(tor, 0); +} + +/*** +**** +**** +**** +***/ + +TEST_F(RenameTest, multifileTorrent) +{ + char* str; + auto constexpr TotalSize = size_t{ 67 }; + auto const expected_files = std::array + { + "Felidae/Felinae/Acinonyx/Cheetah/Chester", + "Felidae/Felinae/Felis/catus/Kyphi", + "Felidae/Felinae/Felis/catus/Saffron", + "Felidae/Pantherinae/Panthera/Tiger/Tony" + }; + auto const expected_contents = std::array + { + "It ain't easy bein' cheesy.\n", + "Inquisitive\n", + "Tough\n", + "They’re Grrrrreat!\n" + }; + + auto* ctor = tr_ctorNew(session_); + auto* tor = createTorrentFromBase64Metainfo(ctor, + "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" + "ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4" + "ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp" + "MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw" + "YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo" + "bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx" + "MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp" + "dmF0ZWkwZWVl"); + EXPECT_TRUE(tr_isTorrent(tor)); + auto const* files = tor->info.files; + + // sanity check the info + EXPECT_STREQ("Felidae", tor->info.name); + EXPECT_EQ(TotalSize, tor->info.totalSize); + EXPECT_EQ(tr_file_index_t{ 4 }, tor->info.fileCount); + + for (tr_file_index_t i = 0; i < 4; ++i) + { + EXPECT_EQ(expected_files[i], files[i].name); + } + + // sanity check the (empty) stats + blockingTorrentVerify(tor); + expectHaveNone(tor, TotalSize); + + // build the local data + createMultifileTorrentContents(tor->currentDir); + + // sanity check the (full) stats + blockingTorrentVerify(tor); + auto const* st = tr_torrentStat(tor); + EXPECT_EQ(TR_STATUS_STOPPED, st->activity); + EXPECT_EQ(TR_STAT_OK, st->error); + EXPECT_EQ(0, st->leftUntilDone); + EXPECT_EQ(0, st->haveUnchecked); + EXPECT_EQ(0, st->desiredAvailable); + EXPECT_EQ(TotalSize, st->sizeWhenDone); + EXPECT_EQ(TotalSize, st->haveValid); + + /** + *** okay! let's test renaming. + **/ + + // rename a leaf... + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus/Kyphi", "placeholder")); + EXPECT_STREQ("Felidae/Felinae/Felis/catus/placeholder", files[1].name); + EXPECT_TRUE(testFileExistsAndConsistsOfThisString(tor, 1, "Inquisitive\n")); + + // ...and back again + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus/placeholder", "Kyphi")); + EXPECT_STREQ("Felidae/Felinae/Felis/catus/Kyphi", files[1].name); + testFileExistsAndConsistsOfThisString(tor, 1, "Inquisitive\n"); + + // rename a branch... + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus", "placeholder")); + EXPECT_EQ(expected_files[0], files[0].name); + EXPECT_STREQ("Felidae/Felinae/Felis/placeholder/Kyphi", files[1].name); + EXPECT_STREQ("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name); + EXPECT_EQ(expected_files[3], files[3].name); + EXPECT_TRUE(testFileExistsAndConsistsOfThisString(tor, 1, expected_contents[1])); + EXPECT_TRUE(testFileExistsAndConsistsOfThisString(tor, 2, expected_contents[2])); + EXPECT_FALSE(files[0].is_renamed); + EXPECT_TRUE(files[1].is_renamed); + EXPECT_TRUE(files[2].is_renamed); + EXPECT_FALSE(files[3].is_renamed); + + // (while the branch is renamed: confirm that the .resume file remembers the changes) + tr_torrentSaveResume(tor); + // this is a bit dodgy code-wise, but let's make sure the .resume file got the name + tr_free(files[1].name); + tor->info.files[1].name = tr_strdup("gabba gabba hey"); + auto const loaded = tr_torrentLoadResume(tor, ~0, ctor, nullptr); + EXPECT_NE(decltype(loaded) { 0 }, (loaded & TR_FR_FILENAMES)); + EXPECT_EQ(expected_files[0], files[0].name); + EXPECT_STREQ("Felidae/Felinae/Felis/placeholder/Kyphi", files[1].name); + EXPECT_STREQ("Felidae/Felinae/Felis/placeholder/Saffron", files[2].name); + EXPECT_EQ(expected_files[3], files[3].name); + + // ...and back again + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/placeholder", "catus")); + + for (tr_file_index_t i = 0; i < 4; ++i) + { + EXPECT_EQ(expected_files[i], files[i].name); + EXPECT_TRUE(testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i])); + } + + EXPECT_FALSE(files[0].is_renamed); + EXPECT_TRUE(files[1].is_renamed); + EXPECT_TRUE(files[2].is_renamed); + EXPECT_FALSE(files[3].is_renamed); + + /*** + **** Test it an incomplete torrent... + ***/ + + // remove the directory Felidae/Felinae/Felis/catus + str = tr_torrentFindFile(tor, 1); + EXPECT_NE(nullptr, str); + tr_sys_path_remove(str, nullptr); + tr_free(str); + str = tr_torrentFindFile(tor, 2); + EXPECT_NE(nullptr, str); + tr_sys_path_remove(str, nullptr); + auto* tmp = tr_sys_path_dirname(str, nullptr); + tr_sys_path_remove(tmp, nullptr); + tr_free(tmp); + tr_free(str); + sync(); + blockingTorrentVerify(tor); + testFileExistsAndConsistsOfThisString(tor, 0, expected_contents[0]); + + for (tr_file_index_t i = 1; i <= 2; ++i) + { + str = tr_torrentFindFile(tor, i); + EXPECT_STREQ(nullptr, str); + tr_free(str); + } + + testFileExistsAndConsistsOfThisString(tor, 3, expected_contents[3]); + + // rename a branch... + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/catus", "foo")); + EXPECT_EQ(expected_files[0], files[0].name); + EXPECT_STREQ("Felidae/Felinae/Felis/foo/Kyphi", files[1].name); + EXPECT_STREQ("Felidae/Felinae/Felis/foo/Saffron", files[2].name); + EXPECT_EQ(expected_files[3], files[3].name); + + // ...and back again + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Felinae/Felis/foo", "catus")); + + for (tr_file_index_t i = 0; i < 4; ++i) + { + EXPECT_EQ(expected_files[i], files[i].name); + } + + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae", "gabba")); + auto strings = std::array{}; + strings[0] = "gabba/Felinae/Acinonyx/Cheetah/Chester"; + strings[1] = "gabba/Felinae/Felis/catus/Kyphi"; + strings[2] = "gabba/Felinae/Felis/catus/Saffron"; + strings[3] = "gabba/Pantherinae/Panthera/Tiger/Tony"; + + for (tr_file_index_t i = 0; i < 4; ++i) + { + EXPECT_STREQ(strings[i], files[i].name); + testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]); + } + + // rename the root, then a branch, and then a leaf... + EXPECT_EQ(0, torrentRenameAndWait(tor, "gabba", "Felidae")); + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Pantherinae/Panthera/Tiger", "Snow Leopard")); + EXPECT_EQ(0, torrentRenameAndWait(tor, "Felidae/Pantherinae/Panthera/Snow Leopard/Tony", "10.6")); + strings[0] = "Felidae/Felinae/Acinonyx/Cheetah/Chester"; + strings[1] = "Felidae/Felinae/Felis/catus/Kyphi"; + strings[2] = "Felidae/Felinae/Felis/catus/Saffron"; + strings[3] = "Felidae/Pantherinae/Panthera/Snow Leopard/10.6"; + + for (tr_file_index_t i = 0; i < 4; ++i) + { + EXPECT_STREQ(strings[i], files[i].name); + testFileExistsAndConsistsOfThisString(tor, i, expected_contents[i]); + } + + tr_ctorFree(ctor); + torrentRemoveAndWait(tor, 0); + + /** + *** Test renaming prefixes (shouldn't work) + **/ + + ctor = tr_ctorNew(session_); + tor = createTorrentFromBase64Metainfo(ctor, + "ZDEwOmNyZWF0ZWQgYnkyNTpUcmFuc21pc3Npb24vMi42MSAoMTM0MDcpMTM6Y3JlYXRpb24gZGF0" + "ZWkxMzU4NTU1NDIwZTg6ZW5jb2Rpbmc1OlVURi04NDppbmZvZDU6ZmlsZXNsZDY6bGVuZ3RoaTI4" + "ZTQ6cGF0aGw3OkZlbGluYWU4OkFjaW5vbnl4NzpDaGVldGFoNzpDaGVzdGVyZWVkNjpsZW5ndGhp" + "MTJlNDpwYXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNTpLeXBoaWVlZDY6bGVuZ3RoaTZlNDpw" + "YXRobDc6RmVsaW5hZTU6RmVsaXM1OmNhdHVzNzpTYWZmcm9uZWVkNjpsZW5ndGhpMjFlNDpwYXRo" + "bDExOlBhbnRoZXJpbmFlODpQYW50aGVyYTU6VGlnZXI0OlRvbnllZWU0Om5hbWU3OkZlbGlkYWUx" + "MjpwaWVjZSBsZW5ndGhpMzI3NjhlNjpwaWVjZXMyMDp27buFkmy8ICfNX4nsJmt0Ckm2Ljc6cHJp" + "dmF0ZWkwZWVl"); + EXPECT_TRUE(tr_isTorrent(tor)); + files = tor->info.files; + + // rename prefix of top + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "Feli", "FelidaeX")); + EXPECT_STREQ("Felidae", tor->info.name); + EXPECT_FALSE(files[0].is_renamed); + EXPECT_FALSE(files[1].is_renamed); + EXPECT_FALSE(files[2].is_renamed); + EXPECT_FALSE(files[3].is_renamed); + + // rename false path + EXPECT_EQ(EINVAL, torrentRenameAndWait(tor, "Felidae/FelinaeX", "Genus Felinae")); + EXPECT_STREQ("Felidae", tor->info.name); + EXPECT_FALSE(files[0].is_renamed); + EXPECT_FALSE(files[1].is_renamed); + EXPECT_FALSE(files[2].is_renamed); + EXPECT_FALSE(files[3].is_renamed); + + /*** + **** + ***/ + + // cleanup + tr_ctorFree(ctor); + torrentRemoveAndWait(tor, 0); +} + +/*** +**** +***/ + +TEST_F(RenameTest, partialFile) +{ + auto constexpr PieceCount = uint32_t { 33 }; + auto constexpr PieceSize = uint32_t { 32768 }; + auto constexpr Length = std::array{ 1048576, 4096, 512 }; + auto constexpr TotalSize = uint64_t(Length[0]) + Length[1] + Length[2]; + + /*** + **** create our test torrent with an incomplete .part file + ***/ + + auto* tor = zeroTorrentInit(); + EXPECT_EQ(TotalSize, tor->info.totalSize); + EXPECT_EQ(PieceSize, tor->info.pieceSize); + EXPECT_EQ(PieceCount, tor->info.pieceCount); + EXPECT_STREQ("files-filled-with-zeroes/1048576", tor->info.files[0].name); + EXPECT_STREQ("files-filled-with-zeroes/4096", tor->info.files[1].name); + EXPECT_STREQ("files-filled-with-zeroes/512", tor->info.files[2].name); + + zeroTorrentPopulate(tor, false); + auto* fst = tr_torrentFiles(tor, nullptr); + EXPECT_EQ(Length[0] - PieceSize, fst[0].bytesCompleted); + EXPECT_EQ(Length[1], fst[1].bytesCompleted); + EXPECT_EQ(Length[2], fst[2].bytesCompleted); + tr_torrentFilesFree(fst, tor->info.fileCount); + auto const* st = tr_torrentStat(tor); + EXPECT_EQ(TotalSize, st->sizeWhenDone); + EXPECT_EQ(PieceSize, st->leftUntilDone); + + /*** + **** + ***/ + + EXPECT_EQ(0, torrentRenameAndWait(tor, "files-filled-with-zeroes", "foo")); + EXPECT_EQ(0, torrentRenameAndWait(tor, "foo/1048576", "bar")); + auto strings = std::array{}; + strings[0] = "foo/bar"; + strings[1] = "foo/4096"; + strings[2] = "foo/512"; + + for (tr_file_index_t i = 0; i < 3; ++i) + { + EXPECT_STREQ(strings[i], tor->info.files[i].name); + } + + strings[0] = "foo/bar.part"; + + for (tr_file_index_t i = 0; i < 3; ++i) + { + char* expected = tr_buildPath(tor->currentDir, strings[i], nullptr); + char* path = tr_torrentFindFile(tor, i); + EXPECT_STREQ(expected, path); + tr_free(path); + tr_free(expected); + } + + torrentRemoveAndWait(tor, 0); +} + +} // namespace test + +} // namespace libtransmission diff --git a/tests/libtransmission/rpc-test.cc b/tests/libtransmission/rpc-test.cc new file mode 100644 index 000000000..b18c45ff6 --- /dev/null +++ b/tests/libtransmission/rpc-test.cc @@ -0,0 +1,187 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "rpcimpl.h" +#include "utils.h" +#include "variant.h" + +#include "test-fixtures.h" + +#include +#include +#include +#include + +namespace libtransmission +{ + +namespace test +{ + +using RpcTest = SessionTest; + +TEST_F(RpcTest, list) +{ + size_t len; + int64_t i; + char const* str; + tr_variant top; + + tr_rpc_parse_list_str(&top, "12", TR_BAD_SIZE); + EXPECT_TRUE(tr_variantIsInt(&top)); + EXPECT_TRUE(tr_variantGetInt(&top, &i)); + EXPECT_EQ(12, i); + tr_variantFree(&top); + + tr_rpc_parse_list_str(&top, "12", 1); + EXPECT_TRUE(tr_variantIsInt(&top)); + EXPECT_TRUE(tr_variantGetInt(&top, &i)); + EXPECT_EQ(1, i); + tr_variantFree(&top); + + tr_rpc_parse_list_str(&top, "6,7", TR_BAD_SIZE); + EXPECT_TRUE(tr_variantIsList(&top)); + EXPECT_EQ(2, tr_variantListSize(&top)); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&top, 0), &i)); + EXPECT_EQ(6, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&top, 1), &i)); + EXPECT_EQ(7, i); + tr_variantFree(&top); + + tr_rpc_parse_list_str(&top, "asdf", TR_BAD_SIZE); + EXPECT_TRUE(tr_variantIsString(&top)); + EXPECT_TRUE(tr_variantGetStr(&top, &str, &len)); + EXPECT_EQ(4, len); + EXPECT_STREQ("asdf", str); + tr_variantFree(&top); + + tr_rpc_parse_list_str(&top, "1,3-5", TR_BAD_SIZE); + EXPECT_TRUE(tr_variantIsList(&top)); + EXPECT_EQ(4, tr_variantListSize(&top)); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&top, 0), &i)); + EXPECT_EQ(1, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&top, 1), &i)); + EXPECT_EQ(3, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&top, 2), &i)); + EXPECT_EQ(4, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&top, 3), &i)); + EXPECT_EQ(5, i); + tr_variantFree(&top); +} + +/*** +**** +***/ + +TEST_F(RpcTest, sessionGet) +{ + auto const rpc_response_func = [] (tr_session* session UNUSED, tr_variant* response, void* setme) noexcept + { + *static_cast(setme) = *response; + tr_variantInitBool(response, false); + }; + + auto* tor = zeroTorrentInit(); + EXPECT_NE(nullptr, tor); + + tr_variant request; + tr_variantInitDict(&request, 1); + tr_variantDictAddStr(&request, TR_KEY_method, "session-get"); + tr_variant response; + tr_rpc_request_exec_json(session_, &request, rpc_response_func, &response); + tr_variantFree(&request); + + EXPECT_TRUE(tr_variantIsDict(&response)); + tr_variant* args; + EXPECT_TRUE(tr_variantDictFindDict(&response, TR_KEY_arguments, &args)); + + // what we expected + auto constexpr ExpectedKeys = std::array{ + TR_KEY_alt_speed_down, + TR_KEY_alt_speed_enabled, + TR_KEY_alt_speed_time_begin, + TR_KEY_alt_speed_time_day, + TR_KEY_alt_speed_time_enabled, + TR_KEY_alt_speed_time_end, + TR_KEY_alt_speed_up, + TR_KEY_blocklist_enabled, + TR_KEY_blocklist_size, + TR_KEY_blocklist_url, + TR_KEY_cache_size_mb, + TR_KEY_config_dir, + TR_KEY_dht_enabled, + TR_KEY_download_dir, + TR_KEY_download_dir_free_space, + TR_KEY_download_queue_enabled, + TR_KEY_download_queue_size, + TR_KEY_encryption, + TR_KEY_idle_seeding_limit, + TR_KEY_idle_seeding_limit_enabled, + TR_KEY_incomplete_dir, + TR_KEY_incomplete_dir_enabled, + TR_KEY_lpd_enabled, + TR_KEY_peer_limit_global, + TR_KEY_peer_limit_per_torrent, + TR_KEY_peer_port, + TR_KEY_peer_port_random_on_start, + TR_KEY_pex_enabled, + TR_KEY_port_forwarding_enabled, + TR_KEY_queue_stalled_enabled, + TR_KEY_queue_stalled_minutes, + TR_KEY_rename_partial_files, + TR_KEY_rpc_version, + TR_KEY_rpc_version_minimum, + TR_KEY_script_torrent_done_enabled, + TR_KEY_script_torrent_done_filename, + TR_KEY_seed_queue_enabled, + TR_KEY_seed_queue_size, + TR_KEY_seedRatioLimit, + TR_KEY_seedRatioLimited, + TR_KEY_session_id, + TR_KEY_speed_limit_down, + TR_KEY_speed_limit_down_enabled, + TR_KEY_speed_limit_up, + TR_KEY_speed_limit_up_enabled, + TR_KEY_start_added_torrents, + TR_KEY_trash_original_torrent_files, + TR_KEY_units, + TR_KEY_utp_enabled, + TR_KEY_version + }; + + // what we got + std::set actual_keys; + tr_quark key; + tr_variant* val; + size_t n = 0; + while ((tr_variantDictChild(args, n++, &key, &val))) + { + actual_keys.insert(key); + } + + auto missing_keys = std::vector{}; + std::set_difference(std::begin(ExpectedKeys), std::end(ExpectedKeys), + std::begin(actual_keys), std::end(actual_keys), + std::inserter(missing_keys, std::begin(missing_keys))); + EXPECT_EQ(decltype(missing_keys) {}, missing_keys); + + auto unexpected_keys = std::vector{}; + std::set_difference(std::begin(actual_keys), std::end(actual_keys), + std::begin(ExpectedKeys), std::end(ExpectedKeys), + std::inserter(unexpected_keys, std::begin(unexpected_keys))); + EXPECT_EQ(decltype(unexpected_keys) {}, unexpected_keys); + + // cleanup + tr_variantFree(&response); + tr_torrentRemove(tor, false, nullptr); +} + +} // namespace test + +} // namespace libtransmission diff --git a/tests/libtransmission/session-test.cc b/tests/libtransmission/session-test.cc new file mode 100644 index 000000000..68d96cb6e --- /dev/null +++ b/tests/libtransmission/session-test.cc @@ -0,0 +1,125 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "session.h" +#include "session-id.h" +#include "utils.h" +#include "version.h" + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include + +TEST(Session, peerId) +{ + auto const peer_id_prefix = std::string { PEERID_PREFIX }; + + for (int i = 0; i < 100000; ++i) + { + // get a new peer-id + auto buf = std::array{}; + tr_peerIdInit(buf.data()); + + // confirm that it has the right length + EXPECT_EQ(PEER_ID_LEN, strlen(reinterpret_cast(buf.data()))); + + // confirm that it begins with peer_id_prefix + auto const peer_id = std::string(reinterpret_cast(buf.data()), PEER_ID_LEN); + EXPECT_EQ(peer_id_prefix, peer_id.substr(0, peer_id_prefix.size())); + + // confirm that its total is evenly divisible by 36 + int val = 0; + auto const suffix = peer_id.substr(peer_id_prefix.size()); + for (char const ch : suffix) + { + auto const tmp = std::array{ ch, '\0' }; + val += strtoul(tmp.data(), nullptr, 36); + } + + EXPECT_EQ(0, val % 36); + } +} + +TEST(Session, sessionId) +{ + EXPECT_FALSE(tr_session_id_is_local(nullptr)); + EXPECT_FALSE(tr_session_id_is_local("")); + EXPECT_FALSE(tr_session_id_is_local("test")); + + auto session_id = tr_session_id_new(); + EXPECT_NE(nullptr, session_id); + + tr_timeUpdate(0); + + auto const* session_id_str_1 = tr_session_id_get_current(session_id); + EXPECT_NE(nullptr, session_id_str_1); + EXPECT_EQ(48, strlen(session_id_str_1)); + session_id_str_1 = tr_strdup(session_id_str_1); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_1)); + + tr_timeUpdate(60 * 60 - 1); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_1)); + + auto const* session_id_str_2 = tr_session_id_get_current(session_id); + EXPECT_NE(nullptr, session_id_str_2); + EXPECT_EQ(48, strlen(session_id_str_2)); + EXPECT_STREQ(session_id_str_1, session_id_str_2); + + tr_timeUpdate(60 * 60); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_1)); + + session_id_str_2 = tr_session_id_get_current(session_id); + EXPECT_NE(nullptr, session_id_str_2); + EXPECT_EQ(48, strlen(session_id_str_2)); + EXPECT_STRNE(session_id_str_1, session_id_str_2); + session_id_str_2 = tr_strdup(session_id_str_2); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_2)); + EXPECT_TRUE(tr_session_id_is_local(session_id_str_1)); + + tr_timeUpdate(60 * 60 * 2); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_2)); + EXPECT_TRUE(tr_session_id_is_local(session_id_str_1)); + + auto const* session_id_str_3 = tr_session_id_get_current(session_id); + EXPECT_NE(nullptr, session_id_str_3); + EXPECT_EQ(48, strlen(session_id_str_3)); + EXPECT_STRNE(session_id_str_2, session_id_str_3); + EXPECT_STRNE(session_id_str_1, session_id_str_3); + session_id_str_3 = tr_strdup(session_id_str_3); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_3)); + EXPECT_TRUE(tr_session_id_is_local(session_id_str_2)); + EXPECT_FALSE(tr_session_id_is_local(session_id_str_1)); + + tr_timeUpdate(60 * 60 * 10); + + EXPECT_TRUE(tr_session_id_is_local(session_id_str_3)); + EXPECT_TRUE(tr_session_id_is_local(session_id_str_2)); + EXPECT_FALSE(tr_session_id_is_local(session_id_str_1)); + + tr_session_id_free(session_id); + + EXPECT_FALSE(tr_session_id_is_local(session_id_str_3)); + EXPECT_FALSE(tr_session_id_is_local(session_id_str_2)); + EXPECT_FALSE(tr_session_id_is_local(session_id_str_1)); + + tr_free(const_cast(session_id_str_3)); + tr_free(const_cast(session_id_str_2)); + tr_free(const_cast(session_id_str_1)); +} diff --git a/tests/libtransmission/subprocess-test.cc b/tests/libtransmission/subprocess-test.cc new file mode 100644 index 000000000..c224da13a --- /dev/null +++ b/tests/libtransmission/subprocess-test.cc @@ -0,0 +1,408 @@ +/* + * This file Copyright (C) 2017 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "error.h" +#include "file.h" +#include "subprocess.h" +#include "utils.h" + +#include "gtest/internal/gtest-port.h" // GetArgvs() + +#include "test-fixtures.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#define setenv(key, value, unused) SetEnvironmentVariableA(key, value) +#endif + +namespace libtransmission +{ + +namespace test +{ + +std::string getSelfPath() +{ + auto const exec = ::testing::internal::GetArgvs().front(); + return makeString(tr_sys_path_resolve(exec.data(), nullptr)); +} + +std::string getCmdSelfPath() +{ + auto const new_suffix = std::string { ".cmd" }; + auto exec = getSelfPath(); + // replace ".exe" suffix with ".cmd" + exec.resize(exec.size() - new_suffix.size()); + exec.append(new_suffix.data(), new_suffix.size()); + return exec; +} + +class SubprocessTest : + public ::testing::Test, + public testing::WithParamInterface +{ +protected: + Sandbox sandbox_; + + [[nodiscard]] std::string buildSandboxPath(std::string const& filename) const + { + auto path = sandbox_.path(); + path += TR_PATH_DELIMITER; + path += filename; + tr_sys_path_native_separators(&path.front()); + return path; + } + + [[nodiscard]] static std::string nativeCwd() + { + auto path = makeString(tr_sys_dir_get_current(nullptr)); + tr_sys_path_native_separators(&path.front()); + return path; + } + + std::string const arg_dump_args_ { "--dump-args" }; + std::string const arg_dump_env_ { "--dump-env" }; + std::string const arg_dump_cwd_ { "--dump-cwd" }; + + std::string self_path_; + + // If command-line args were passed in, then this test is being + // invoked as a subprocess: it should dump the info requested by + // the command-line flags and then exit without running tests. + // FIXME: cleanup does not happen when we exit(). move this all + // to a standalone file similar to the .cmd file on Windows + void processCommandLineArgs() const + { + auto const argv = ::testing::internal::GetArgvs(); + if (argv.size() < 3) + { + return; + } + + auto const& result_path = argv[1]; + auto const& test_action = argv[2]; + auto const tmp_result_path = result_path + ".tmp"; + + auto fd = tr_sys_file_open(tmp_result_path.data(), // NOLINT + TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, + 0644, nullptr); + + if (fd == TR_BAD_SYS_FILE) + { + exit(1); + } + + if (test_action == arg_dump_args_) + { + for (size_t i = 3; i < argv.size(); ++i) + { + tr_sys_file_write_line(fd, argv[i].data(), nullptr); + } + } + else if (test_action == arg_dump_env_) + { + for (size_t i = 3; i < argv.size(); ++i) + { + auto const value = makeString(tr_env_get_string(argv[i].data(), "")); + tr_sys_file_write_line(fd, value.data(), nullptr); + } + } + else if (test_action == arg_dump_cwd_) + { + char* const value = tr_sys_dir_get_current(nullptr); + tr_sys_file_write_line(fd, value != nullptr ? value : "", nullptr); + tr_free(value); + } + else + { + tr_sys_file_close(fd, nullptr); + tr_sys_path_remove(tmp_result_path.data(), nullptr); + exit(1); + } + + tr_sys_file_close(fd, nullptr); + tr_sys_path_rename(tmp_result_path.data(), result_path.data(), nullptr); + exit(0); + } + + void waitForFileToExist(std::string const& path) + { + auto const test = [path]() { return tr_sys_path_exists(path.data(), nullptr); }; + EXPECT_TRUE(waitFor(test, 2000)); + } + + void SetUp() override + { + processCommandLineArgs(); + self_path_ = GetParam(); + } +}; + +TEST_P(SubprocessTest, SpawnAsyncMissingExec) +{ + auto const missing_exe_path = std::string { TR_IF_WIN32("C:\\", "/") "tr-missing-test-exe" TR_IF_WIN32(".exe", "") }; + + auto args = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup(missing_exe_path.data()), + nullptr + }; + + tr_error* error = nullptr; + auto const ret = tr_spawn_async(args.data(), nullptr, nullptr, &error); + EXPECT_FALSE(ret); + EXPECT_NE(nullptr, error); + EXPECT_NE(0, error->code); + EXPECT_NE(nullptr, error->message); + + tr_error_clear(&error); +} + +TEST_P(SubprocessTest, SpawnAsyncArgs) +{ + auto const result_path = buildSandboxPath("result.txt"); + bool const allow_batch_metachars = TR_IF_WIN32(false, true) || !tr_str_has_suffix(self_path_.c_str(), ".cmd"); + + auto const test_arg1 = std::string { "arg1 " }; + auto const test_arg2 = std::string { " arg2" }; + auto const test_arg3 = std::string {}; + auto const test_arg4 = std::string { "\"arg3'^! $PATH %PATH% \\" }; + + auto args = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup(self_path_.c_str()), + tr_strdup(result_path.data()), + tr_strdup(arg_dump_args_.data()), + tr_strdup(test_arg1.data()), + tr_strdup(test_arg2.data()), + tr_strdup(test_arg3.data()), + tr_strdup(allow_batch_metachars ? test_arg4.data() : nullptr), + nullptr + }; + + tr_error* error = nullptr; + bool const ret = tr_spawn_async(args.data(), nullptr, nullptr, &error); + EXPECT_TRUE(ret) << args[0] << ' ' << args[1]; + EXPECT_EQ(nullptr, error) << error->code << ", " << error->message; + + waitForFileToExist(result_path); + + auto fd = tr_sys_file_open(result_path.data(), TR_SYS_FILE_READ, 0, nullptr); // NOLINT + EXPECT_NE(TR_BAD_SYS_FILE, fd); + + auto buffer = std::array{}; + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_arg1, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_arg2, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_arg3, buffer.data()); + + if (allow_batch_metachars) + { + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_arg4, buffer.data()); + } + + EXPECT_FALSE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + + tr_sys_file_close(fd, nullptr); +} + +TEST_P(SubprocessTest, SpawnAsyncEnv) +{ + auto const result_path = buildSandboxPath("result.txt"); + + auto const test_env_key1 = std::string { "VAR1" }; + auto const test_env_key2 = std::string { "_VAR_2_" }; + auto const test_env_key3 = std::string { "vAr#" }; + auto const test_env_key4 = std::string { "FOO" }; + auto const test_env_key5 = std::string { "ZOO" }; + auto const test_env_key6 = std::string { "TR_MISSING_TEST_ENV_KEY" }; + + auto const test_env_value1 = std::string { "value1 " }; + auto const test_env_value2 = std::string { " value2" }; + auto const test_env_value3 = std::string { " \"value3'^! $PATH %PATH% " }; + auto const test_env_value4 = std::string { "bar" }; + auto const test_env_value5 = std::string { "jar" }; + + auto args = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup(self_path_.c_str()), + tr_strdup(result_path.data()), + tr_strdup(arg_dump_env_.data()), + tr_strdup(test_env_key1.data()), + tr_strdup(test_env_key2.data()), + tr_strdup(test_env_key3.data()), + tr_strdup(test_env_key4.data()), + tr_strdup(test_env_key5.data()), + tr_strdup(test_env_key6.data()), + nullptr + }; + + auto env = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup_printf("%s=%s", test_env_key1.data(), test_env_value1.data()), + tr_strdup_printf("%s=%s", test_env_key2.data(), test_env_value2.data()), + tr_strdup_printf("%s=%s", test_env_key3.data(), test_env_value3.data()), + tr_strdup_printf("%s=%s", test_env_key5.data(), test_env_value5.data()), + nullptr + }; + + setenv("FOO", "bar", true); // inherited + setenv("ZOO", "tar", true); // overridden + + tr_error* error = nullptr; + bool const ret = tr_spawn_async(args.data(), env.data(), nullptr, &error); + EXPECT_TRUE(ret); + EXPECT_EQ(nullptr, error); + + waitForFileToExist(result_path); + + auto fd = tr_sys_file_open(result_path.data(), TR_SYS_FILE_READ, 0, nullptr); // NOLINT + EXPECT_NE(TR_BAD_SYS_FILE, fd); + + auto buffer = std::array{}; + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_env_value1, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_env_value2, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_env_value3, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_env_value4, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(test_env_value5, buffer.data()); + + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_STREQ("", buffer.data()); + + EXPECT_FALSE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + + tr_sys_file_close(fd, nullptr); + + for (auto& env_item : env) + { + tr_free(env_item); + } +} + +TEST_P(SubprocessTest, SpawnAsyncCwdExplicit) +{ + auto const test_dir = sandbox_.path(); + auto const result_path = buildSandboxPath("result.txt"); + + auto args = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup(self_path_.c_str()), + tr_strdup(result_path.data()), + tr_strdup(arg_dump_cwd_.data()), + nullptr + }; + + tr_error* error = nullptr; + bool const ret = tr_spawn_async(args.data(), nullptr, test_dir.c_str(), &error); + EXPECT_TRUE(ret); + EXPECT_EQ(nullptr, error); + + waitForFileToExist(result_path); + + auto fd = tr_sys_file_open(result_path.data(), TR_SYS_FILE_READ, 0, nullptr); // NOLINT + EXPECT_NE(TR_BAD_SYS_FILE, fd); + + auto buffer = std::array{}; + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(makeString(tr_sys_path_native_separators(tr_strdup(test_dir.c_str()))), + tr_sys_path_native_separators(&buffer.front())); + + EXPECT_FALSE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + + tr_sys_file_close(fd, nullptr); +} + +TEST_P(SubprocessTest, SpawnAsyncCwdInherit) +{ + auto const result_path = buildSandboxPath("result.txt"); + auto const expected_cwd = nativeCwd(); + + auto args = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup(self_path_.c_str()), + tr_strdup(result_path.data()), + tr_strdup(arg_dump_cwd_.data()), + nullptr + }; + + tr_error* error = nullptr; + auto const ret = tr_spawn_async(args.data(), nullptr, nullptr, &error); + EXPECT_TRUE(ret); + EXPECT_EQ(nullptr, error); + + waitForFileToExist(result_path); + auto fd = tr_sys_file_open(result_path.data(), TR_SYS_FILE_READ, 0, nullptr); // NOLINT + EXPECT_NE(TR_BAD_SYS_FILE, fd); + + auto buffer = std::array{}; + EXPECT_TRUE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + EXPECT_EQ(expected_cwd, tr_sys_path_native_separators(&buffer.front())); + EXPECT_FALSE(tr_sys_file_read_line(fd, buffer.data(), buffer.size(), nullptr)); + + tr_sys_file_close(fd, nullptr); +} + +TEST_P(SubprocessTest, SpawnAsyncCwdMissing) +{ + auto const result_path = buildSandboxPath("result.txt"); + + auto args = std::array{ + // FIXME(ckerr): remove tr_strdup()s after https://github.com/transmission/transmission/issues/1384 + tr_strdup(self_path_.c_str()), + tr_strdup(result_path.data()), + tr_strdup(arg_dump_cwd_.data()), + nullptr + }; + + tr_error* error = nullptr; + auto const ret = tr_spawn_async(args.data(), nullptr, TR_IF_WIN32("C:\\", "/") "tr-missing-test-work-dir", &error); + EXPECT_FALSE(ret); + EXPECT_NE(nullptr, error); + EXPECT_NE(0, error->code); + EXPECT_NE(nullptr, error->message); + tr_error_clear(&error); +} + +#ifdef _WIN32 +INSTANTIATE_TEST_SUITE_P( + Subprocess, + SubprocessTest, + ::testing::Values(getSelfPath(), getCmdSelfPath()) + ); +#else +INSTANTIATE_TEST_SUITE_P( + Subprocess, + SubprocessTest, + ::testing::Values(getSelfPath()) + ); +#endif + +} // namespace test + +} // namespace libtransmission diff --git a/libtransmission/subprocess-test.cmd b/tests/libtransmission/subprocess-test.cmd similarity index 100% rename from libtransmission/subprocess-test.cmd rename to tests/libtransmission/subprocess-test.cmd diff --git a/tests/libtransmission/test-fixtures.h b/tests/libtransmission/test-fixtures.h new file mode 100644 index 000000000..2e0108ef4 --- /dev/null +++ b/tests/libtransmission/test-fixtures.h @@ -0,0 +1,484 @@ +/* + * 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 "crypto-utils.h" // tr_base64_decode_str() +#include "error.h" +#include "file.h" // tr_sys_file_*() +#include "quark.h" +#include "platform.h" // TR_PATH_DELIMITER +#include "trevent.h" // tr_amInEventThread() +#include "torrent.h" +#include "variant.h" + +#include +#include // strlen() +#include +#include +#include // std::once_flag() +#include +#include // getenv() + +#include "gtest/gtest.h" + +namespace libtransmission +{ + +namespace test +{ + +auto const makeString = [](char*&& s) + { + auto const ret = std::string(s != nullptr ? s : ""); + tr_free(s); + return ret; + }; + +bool waitFor(std::function const& test, int msec) +{ + auto const deadline = std::chrono::milliseconds { msec }; + auto const begin = std::chrono::steady_clock::now(); + + for (;;) + { + if (test()) + { + return true; + } + + if ((std::chrono::steady_clock::now() - begin) >= deadline) + { + return false; + } + + tr_wait_msec(10); + } +} + +class Sandbox +{ +public: + Sandbox() : + parent_dir_{get_default_parent_dir()}, + sandbox_dir_{create_sandbox(parent_dir_, "transmission-test-XXXXXX")} + { + } + + ~Sandbox() + { + rimraf(sandbox_dir_); + } + + std::string const& path() const + { + return sandbox_dir_; + } + +protected: + static std::string get_default_parent_dir() + { + auto* path = getenv("TMPDIR"); + if (path != NULL) + { + return path; + } + + tr_error* error = nullptr; + path = tr_sys_dir_get_current(&error); + if (path != nullptr) + { + std::string const ret = path; + tr_free(path); + return ret; + } + + std::cerr << "tr_sys_dir_get_current error: '" << error->message << "'" << std::endl; + tr_error_free(error); + return {}; + } + + static std::string create_sandbox(std::string const& parent_dir, std::string const& tmpl) + { + std::string path = makeString(tr_buildPath(parent_dir.data(), tmpl.data(), nullptr)); + tr_sys_dir_create_temp(&path.front(), nullptr); + tr_sys_path_native_separators(&path.front()); + return path; + } + + static auto get_folder_files(std::string const& path) + { + std::vector ret; + + tr_sys_path_info info; + if (tr_sys_path_get_info(path.data(), 0, &info, nullptr) && + (info.type == TR_SYS_PATH_IS_DIRECTORY)) + { + auto const odir = tr_sys_dir_open(path.data(), nullptr); + if (odir != TR_BAD_SYS_DIR) + { + char const* name; + while ((name = tr_sys_dir_read_name(odir, nullptr)) != nullptr) + { + if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) + { + ret.push_back(makeString(tr_buildPath(path.data(), name, nullptr))); + } + } + + tr_sys_dir_close(odir, nullptr); + } + } + + return ret; + } + + static void rimraf(std::string const& path, bool verbose = false) + { + for (auto const& child : get_folder_files(path)) + { + rimraf(child, verbose); + } + + if (verbose) + { + std::cerr << "cleanup: removing '" << path << "'" << std::endl; + } + + tr_sys_path_remove(path.data(), nullptr); + } + +private: + std::string const parent_dir_; + std::string const sandbox_dir_; +}; + +class SandboxedTest : public ::testing::Test +{ +protected: + std::string sandboxDir() const { return sandbox_.path(); } + + auto currentTestName() const + { + auto const* i = ::testing::UnitTest::GetInstance()->current_test_info(); + auto child = std::string(i->test_suite_name()); + child += '_'; + child += i->name(); + return child; + } + + void buildParentDir(std::string const& path) const + { + auto const tmperr = errno; + + auto const dir = makeString(tr_sys_path_dirname(path.c_str(), nullptr)); + tr_error* error = nullptr; + tr_sys_dir_create(dir.data(), TR_SYS_DIR_CREATE_PARENTS, 0700, &error); + EXPECT_EQ(nullptr, error) << "path[" << path << "] dir[" << dir << "] " << error->code << ", " << error->message; + + errno = tmperr; + } + + static void blockingFileWrite(tr_sys_file_t fd, void const* data, size_t data_len) + { + uint64_t n_left = data_len; + auto const* left = static_cast(data); + + while (n_left > 0) + { + uint64_t n = {}; + tr_error* error = nullptr; + if (!tr_sys_file_write(fd, left, n_left, &n, &error)) + { + fprintf(stderr, "Error writing file: '%s'\n", error->message); + tr_error_free(error); + break; + } + + left += n; + n_left -= n; + } + } + + void createTmpfileWithContents(std::string& tmpl, void const* payload, size_t n) const + { + auto const tmperr = errno; + + buildParentDir(tmpl); + + // NOLINTNEXTLINE(clang-analyzer-cplusplus.InnerPointer) + auto const fd = tr_sys_file_open_temp(&tmpl.front(), nullptr); + blockingFileWrite(fd, payload, n); + tr_sys_file_close(fd, nullptr); + sync(); + + errno = tmperr; + } + + void createFileWithContents(std::string const& path, void const* payload, size_t n) const + { + auto const tmperr = errno; + + buildParentDir(path); + + auto const fd = tr_sys_file_open(path.c_str(), + TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, + 0600, nullptr); + blockingFileWrite(fd, payload, n); + tr_sys_file_close(fd, nullptr); + sync(); + + errno = tmperr; + } + + void createFileWithContents(std::string const& path, void const* payload) const + { + createFileWithContents(path, payload, strlen(static_cast(payload))); + } + + bool verbose = false; + + void sync() const + { +#ifndef _WIN32 + ::sync(); +#endif + } + +private: + Sandbox sandbox_; +}; + +void ensureFormattersInited() +{ + static constexpr int MEM_K = 1024; + static char const constexpr* const MEM_K_STR = "KiB"; + static char const constexpr* const MEM_M_STR = "MiB"; + static char const constexpr* const MEM_G_STR = "GiB"; + static char const constexpr* const MEM_T_STR = "TiB"; + + static constexpr int DISK_K = 1000; + static char const constexpr* const DISK_K_STR = "kB"; + static char const constexpr* const DISK_M_STR = "MB"; + static char const constexpr* const DISK_G_STR = "GB"; + static char const constexpr* const DISK_T_STR = "TB"; + + static constexpr int SPEED_K = 1000; + static char const constexpr* const SPEED_K_STR = "kB/s"; + static char const constexpr* const SPEED_M_STR = "MB/s"; + static char const constexpr* const SPEED_G_STR = "GB/s"; + static char const constexpr* const SPEED_T_STR = "TB/s"; + + static std::once_flag flag; + + std::call_once(flag, []() + { + 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); + }); +} + +class SessionTest : public SandboxedTest +{ +private: + std::shared_ptr settings_; + + tr_session* sessionInit(tr_variant* settings) + { + ensureFormattersInited(); + + // download dir + size_t len; + char const* str; + auto q = TR_KEY_download_dir; + auto const download_dir = tr_variantDictFindStr(settings, q, &str, &len) ? + makeString(tr_strdup_printf("%s/%*.*s", sandboxDir().data(), (int)len, (int)len, str)) : + makeString(tr_buildPath(sandboxDir().data(), "Downloads", nullptr)); + tr_sys_dir_create(download_dir.data(), TR_SYS_DIR_CREATE_PARENTS, 0700, nullptr); + tr_variantDictAddStr(settings, q, download_dir.data()); + + // incomplete dir + q = TR_KEY_incomplete_dir; + auto const incomplete_dir = tr_variantDictFindStr(settings, q, &str, &len) ? + makeString(tr_strdup_printf("%s/%*.*s", sandboxDir().data(), (int)len, (int)len, str)) : + makeString(tr_buildPath(sandboxDir().data(), "Incomplete", nullptr)); + tr_variantDictAddStr(settings, q, incomplete_dir.data()); + + // blocklists + auto const blocklist_dir = makeString(tr_buildPath(sandboxDir().data(), "blocklists", nullptr)); + tr_sys_dir_create(blocklist_dir.data(), TR_SYS_DIR_CREATE_PARENTS, 0700, nullptr); + + // fill in any missing settings + + q = TR_KEY_port_forwarding_enabled; + if (tr_variantDictFind(settings, q) == nullptr) + { + tr_variantDictAddBool(settings, q, false); + } + + q = TR_KEY_dht_enabled; + if (tr_variantDictFind(settings, q) == nullptr) + { + tr_variantDictAddBool(settings, q, false); + } + + q = TR_KEY_message_level; + if (tr_variantDictFind(settings, q) == nullptr) + { + tr_variantDictAddInt(settings, q, verbose ? TR_LOG_DEBUG : TR_LOG_ERROR); + } + + return tr_sessionInit(sandboxDir().data(), !verbose, settings); + } + + void sessionClose(tr_session* session) + { + tr_sessionClose(session); + tr_logFreeQueue(tr_logGetQueue()); + } + +protected: + tr_torrent* zeroTorrentInit() const + { + // 1048576 files-filled-with-zeroes/1048576 + // 4096 files-filled-with-zeroes/4096 + // 512 files-filled-with-zeroes/512 + char const* 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 + auto metainfo_len = size_t{}; + auto* metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len); + EXPECT_NE(nullptr, metainfo); + EXPECT_LT(size_t{ 0 }, metainfo_len); + auto* ctor = tr_ctorNew(session_); + tr_ctorSetMetainfo(ctor, reinterpret_cast(metainfo), metainfo_len); + tr_ctorSetPaused(ctor, TR_FORCE, true); + tr_free(metainfo); + + // create the torrent + auto err = int{}; + auto* tor = tr_torrentNew(ctor, &err, nullptr); + EXPECT_EQ(0, err); + + // cleanup + tr_ctorFree(ctor); + return tor; + } + + void zeroTorrentPopulate(tr_torrent* tor, bool complete) + { + for (tr_file_index_t i = 0; i < tor->info.fileCount; ++i) + { + auto const& file = tor->info.files[i]; + + auto path = (!complete && i == 0) ? + makeString(tr_strdup_printf("%s%c%s.part", tor->currentDir, TR_PATH_DELIMITER, file.name)) : + makeString(tr_strdup_printf("%s%c%s", tor->currentDir, TR_PATH_DELIMITER, file.name)); + + auto const dirname = makeString(tr_sys_path_dirname(path.c_str(), nullptr)); + tr_sys_dir_create(dirname.data(), TR_SYS_DIR_CREATE_PARENTS, 0700, nullptr); + auto fd = tr_sys_file_open( + path.c_str(), TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE, 0600, nullptr); + + for (uint64_t j = 0; j < file.length; ++j) + { + tr_sys_file_write(fd, (!complete && i == 0 && j < tor->info.pieceSize) ? "\1" : "\0", 1, nullptr, nullptr); + } + + tr_sys_file_close(fd, nullptr); + + path = makeString(tr_torrentFindFile(tor, i)); + auto const err = errno; + EXPECT_TRUE(tr_sys_path_exists(path.c_str(), nullptr)); + errno = err; + } + + sync(); + blockingTorrentVerify(tor); + + if (complete) + { + EXPECT_EQ(0, tr_torrentStat(tor)->leftUntilDone); + } + else + { + EXPECT_EQ(tor->info.pieceSize, tr_torrentStat(tor)->leftUntilDone); + } + } + + void blockingTorrentVerify(tr_torrent* tor) + { + EXPECT_NE(nullptr, tor->session); + EXPECT_FALSE(tr_amInEventThread(tor->session)); + + auto constexpr onVerifyDone = [] (tr_torrent*, bool, void* done) noexcept + { + *static_cast(done) = true; + }; + + bool done = false; + tr_torrentVerify(tor, onVerifyDone, &done); + auto test = [&done]() { return done; }; + EXPECT_TRUE(waitFor(test, 2000)); + } + + tr_session* session_ = nullptr; + + tr_variant* settings() + { + if (!settings_) + { + auto* settings = new tr_variant {}; + tr_variantInitDict(settings, 10); + auto constexpr deleter = [](tr_variant* v) + { + tr_variantFree(v); + delete v; + }; + settings_.reset(settings, deleter); + } + + return settings_.get(); + } + + virtual void SetUp() override + { + session_ = sessionInit(settings()); + SandboxedTest::SetUp(); + } + + virtual void TearDown() override + { + sessionClose(session_); + session_ = nullptr; + settings_.reset(); + + SandboxedTest::TearDown(); + } +}; + +} // namespace test + +} // namespace libtransmission diff --git a/tests/libtransmission/utils-test.cc b/tests/libtransmission/utils-test.cc new file mode 100644 index 000000000..19e55ad89 --- /dev/null +++ b/tests/libtransmission/utils-test.cc @@ -0,0 +1,444 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#ifdef _WIN32 +#include +#define setenv(key, value, unused) SetEnvironmentVariableA(key, value) +#define unsetenv(key) SetEnvironmentVariableA(key, nullptr) +#endif + +#include "transmission.h" +#include "ConvertUTF.h" // tr_utf8_validate() +#include "platform.h" +#include "crypto-utils.h" // tr_rand_int_weak() +#include "utils.h" +#include "web.h" // tr_http_unescape() + +#include "test-fixtures.h" + +#include +#include +#include // sqrt() +#include // setenv(), unsetenv() +#include + +using ::libtransmission::test::makeString; + +using UtilsTest = ::testing::Test; + +TEST_F(UtilsTest, trStripPositionalArgs) +{ + auto const* in = "Hello %1$s foo %2$.*f"; + auto const* expected = "Hello %s foo %.*f"; + auto const* out = tr_strip_positional_args(in); + EXPECT_STREQ(expected, out); + + in = "Hello %1$'d foo %2$'f"; + expected = "Hello %d foo %f"; + out = tr_strip_positional_args(in); + EXPECT_STREQ(expected, out); +} + +TEST_F(UtilsTest, trStrstrip) +{ + auto* in = tr_strdup(" test "); + auto* out = tr_strstrip(in); + EXPECT_EQ(in, out); + EXPECT_STREQ("test", out); + tr_free(in); + + in = tr_strdup(" test test "); + out = tr_strstrip(in); + EXPECT_EQ(in, out); + EXPECT_STREQ("test test", out); + tr_free(in); + + /* strstrip */ + in = tr_strdup("test"); + out = tr_strstrip(in); + EXPECT_EQ(in, out); + EXPECT_STREQ("test", out); + tr_free(in); +} + +TEST_F(UtilsTest, trStrjoin) +{ + auto const in1 = std::array{ "one", "two" }; + auto out = makeString(tr_strjoin(in1.data(), in1.size(), ", ")); + EXPECT_EQ("one, two", out); + + auto const in2 = std::array{ "hello" }; + out = makeString(tr_strjoin(in2.data(), in2.size(), "###")); + EXPECT_EQ("hello", out); + + auto const in3 = std::array{ "a", "b", "ccc", "d", "eeeee" }; + out = makeString(tr_strjoin(in3.data(), in3.size(), " ")); + EXPECT_EQ("a b ccc d eeeee", out); + + auto const in4 = std::array{ "7", "ate", "9" }; + out = makeString(tr_strjoin(in4.data(), in4.size(), "")); + EXPECT_EQ("7ate9", out); + + char const** in5 = nullptr; + out = makeString(tr_strjoin(in5, 0, "a")); + EXPECT_EQ("", out); +} + +TEST_F(UtilsTest, trBuildpath) +{ + auto out = makeString(tr_buildPath("foo", "bar", nullptr)); + EXPECT_EQ("foo" TR_PATH_DELIMITER_STR "bar", out); + + out = makeString(tr_buildPath("", "foo", "bar", nullptr)); + EXPECT_EQ(TR_PATH_DELIMITER_STR "foo" TR_PATH_DELIMITER_STR "bar", out); +} + +TEST_F(UtilsTest, trUtf8clean) +{ + auto const* in = "hello world"; + auto out = makeString(tr_utf8clean(in, TR_BAD_SIZE)); + EXPECT_EQ(in, out); + + in = "hello world"; + out = makeString(tr_utf8clean(in, 5)); + EXPECT_EQ("hello", out); + + // this version is not utf-8 (but cp866) + in = "\x92\xE0\xE3\xA4\xAD\xAE \xA1\xEB\xE2\xEC \x81\xAE\xA3\xAE\xAC"; + out = makeString(tr_utf8clean(in, 17)); + EXPECT_TRUE(out.size() == 17 || out.size() == 33); + EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr)); + + // same string, but utf-8 clean + in = "Трудно быть Богом"; + out = makeString(tr_utf8clean(in, TR_BAD_SIZE)); + EXPECT_NE(nullptr, out.data()); + EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr)); + EXPECT_EQ(in, out); + + in = "\xF4\x00\x81\x82"; + out = makeString(tr_utf8clean(in, 4)); + EXPECT_NE(nullptr, out.data()); + EXPECT_TRUE(out.size() == 1 || out.size() == 2); + EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr)); + + in = "\xF4\x33\x81\x82"; + out = makeString(tr_utf8clean(in, 4)); + EXPECT_NE(nullptr, out.data()); + EXPECT_TRUE(out.size() == 4 || out.size() == 7); + EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr)); +} + +TEST_F(UtilsTest, numbers) +{ + auto count = int{}; + auto* numbers = tr_parseNumberRange("1-10,13,16-19", TR_BAD_SIZE, &count); + EXPECT_EQ(15, count); + EXPECT_EQ(1, numbers[0]); + EXPECT_EQ(6, numbers[5]); + EXPECT_EQ(10, numbers[9]); + EXPECT_EQ(13, numbers[10]); + EXPECT_EQ(16, numbers[11]); + EXPECT_EQ(19, numbers[14]); + tr_free(numbers); + + numbers = tr_parseNumberRange("1-5,3-7,2-6", TR_BAD_SIZE, &count); + EXPECT_EQ(7, count); + EXPECT_NE(nullptr, numbers); + for (int i = 0; i < count; ++i) + { + EXPECT_EQ(i + 1, numbers[i]); + } + + tr_free(numbers); + + numbers = tr_parseNumberRange("1-Hello", TR_BAD_SIZE, &count); + EXPECT_EQ(0, count); + EXPECT_EQ(nullptr, numbers); + + numbers = tr_parseNumberRange("1-", TR_BAD_SIZE, &count); + EXPECT_EQ(0, count); + EXPECT_EQ(nullptr, numbers); + + numbers = tr_parseNumberRange("Hello", TR_BAD_SIZE, &count); + EXPECT_EQ(0, count); + EXPECT_EQ(nullptr, numbers); +} + +namespace +{ + +int compareInts(void const* va, void const* vb) noexcept +{ + auto const a = *static_cast(va); + auto const b = *static_cast(vb); + return a - b; +} + +} // unnamed namespace + +TEST_F(UtilsTest, lowerbound) +{ + auto constexpr A = std::array{ 1, 2, 3, 3, 3, 5, 8 }; + auto const expected_pos = std::array{ 0, 1, 2, 5, 5, 6, 6, 6, 7, 7 }; + auto const expected_exact = std::array{ true, true, true, false, true, false, false, true, false, false }; + + for (int i = 1; i <= 10; i++) + { + bool exact; + auto const pos = tr_lowerBound(&i, A.data(), A.size(), sizeof(int), compareInts, &exact); + EXPECT_EQ(expected_pos[i - 1], pos); + EXPECT_EQ(expected_exact[i - 1], exact); + } +} + +TEST_F(UtilsTest, trQuickfindfirstk) +{ + auto const run_test = [](size_t const k, size_t const n, int* buf, int range) + { + // populate buf with random ints + std::generate(buf, buf + n, [range]() { return tr_rand_int_weak(range); }); + + // find the best k + tr_quickfindFirstK(buf, n, sizeof(int), compareInts, k); + + // confirm that the smallest K ints are in the first slots K slots in buf + auto const* highest_low = std::max_element(buf, buf + k); + auto const* lowest_high = std::min_element(buf + k, buf + n); + EXPECT_LE(highest_low, lowest_high); + }; + + auto constexpr K = size_t{ 10 }; + auto constexpr NumTrials = size_t{ 1000 }; + auto buf = std::array{}; + for (auto i = 0; i != NumTrials; ++i) + { + run_test(K, buf.size(), buf.data(), 100); + } +} + +TEST_F(UtilsTest, trMemmem) +{ + auto const haystack = std::string { "abcabcabcabc" }; + auto const needle = std::string { "cab" }; + + EXPECT_EQ(haystack, tr_memmem(haystack.data(), haystack.size(), haystack.data(), haystack.size())); + EXPECT_EQ(haystack.substr(2), tr_memmem(haystack.data(), haystack.size(), needle.data(), needle.size())); + EXPECT_EQ(nullptr, tr_memmem(needle.data(), needle.size(), haystack.data(), haystack.size())); +} + +TEST_F(UtilsTest, trBinaryHex) +{ + auto const hex_in = std::string { "fb5ef5507427b17e04b69cef31fa3379b456735a" }; + + auto binary = std::array{}; + tr_hex_to_binary(hex_in.data(), binary.data(), hex_in.size() / 2); + + auto hex_out = std::array{}; + tr_binary_to_hex(binary.data(), hex_out.data(), 20); + EXPECT_EQ(hex_in, reinterpret_cast(hex_out.data())); +} + +TEST_F(UtilsTest, array) +{ + auto array = std::array{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + auto n = array.size(); + + tr_removeElementFromArray(array.data(), 5U, sizeof(size_t), n); + --n; + + for (size_t i = 0; i < n; ++i) + { + EXPECT_EQ(array[i], i < 5 ? i : i + 1); + } + + tr_removeElementFromArray(array.data(), 0U, sizeof(size_t), n); + --n; + + for (size_t i = 0; i < n; ++i) + { + EXPECT_EQ(array[i], i < 4 ? i + 1 : i + 2); + } + + tr_removeElementFromArray(array.data(), n - 1, sizeof(size_t), n); + --n; + + for (size_t i = 0; i < n; ++i) + { + EXPECT_EQ(array[i], i < 4 ? i + 1 : i + 2); + } +} + +TEST_F(UtilsTest, url) +{ + auto const* url = "http://1"; + int port; + char* scheme; + char* host; + char* path; + EXPECT_TRUE(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); + EXPECT_STREQ("http", scheme); + EXPECT_STREQ("1", host); + EXPECT_STREQ("/", path); + EXPECT_EQ(80, port); + tr_free(scheme); + tr_free(path); + tr_free(host); + + url = "http://www.some-tracker.org/some/path"; + EXPECT_TRUE(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); + EXPECT_STREQ("http", scheme); + EXPECT_STREQ("www.some-tracker.org", host); + EXPECT_STREQ("/some/path", path); + EXPECT_EQ(80, port); + tr_free(scheme); + tr_free(path); + tr_free(host); + + url = "http://www.some-tracker.org:8080/some/path"; + EXPECT_TRUE(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); + EXPECT_STREQ("http", scheme); + EXPECT_STREQ("www.some-tracker.org", host); + EXPECT_STREQ("/some/path", path); + EXPECT_EQ(8080, port); + tr_free(scheme); + tr_free(path); + tr_free(host); +} + +TEST_F(UtilsTest, trHttpUnescape) +{ + auto const url = std::string { "http%3A%2F%2Fwww.example.com%2F~user%2F%3Ftest%3D1%26test1%3D2" }; + auto str = makeString(tr_http_unescape(url.data(), url.size())); + EXPECT_EQ("http://www.example.com/~user/?test=1&test1=2", str); +} + +TEST_F(UtilsTest, truncd) +{ + auto buf = std::array{}; + + tr_snprintf(buf.data(), buf.size(), "%.2f%%", 99.999); + EXPECT_STREQ("100.00%", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.2f%%", tr_truncd(99.999, 2)); + EXPECT_STREQ("99.99%", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.4f", tr_truncd(403650.656250, 4)); + EXPECT_STREQ("403650.6562", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.2f", tr_truncd(2.15, 2)); + EXPECT_STREQ("2.15", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.2f", tr_truncd(2.05, 2)); + EXPECT_STREQ("2.05", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.2f", tr_truncd(3.3333, 2)); + EXPECT_STREQ("3.33", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.0f", tr_truncd(3.3333, 0)); + EXPECT_STREQ("3", buf.data()); + + tr_snprintf(buf.data(), buf.size(), "%.0f", tr_truncd(3.9999, 0)); + EXPECT_STREQ("3", buf.data()); + +#if !(defined(_MSC_VER) || (defined(__MINGW32__) && defined(__MSVCRT__))) + /* FIXME: MSCVRT behaves differently in case of nan */ + auto const nan = sqrt(-1.0); + tr_snprintf(buf.data(), buf.size(), "%.2f", tr_truncd(nan, 2)); + EXPECT_TRUE(strstr(buf.data(), "nan") != nullptr || strstr(buf.data(), "NaN") != nullptr); +#endif +} + +namespace +{ + +char* testStrdupPrintfValist(char const* fmt, ...) TR_GNUC_PRINTF(1, 2); + +char* testStrdupPrintfValist(char const* fmt, ...) +{ + va_list args; + va_start(args, fmt); + auto* ret = tr_strdup_vprintf(fmt, args); + va_end(args); + return ret; +} + +} // unnamed namespace + +TEST_F(UtilsTest, trStrdupVprintf) +{ + // NOLINTNEXTLINE(cert-dcl50-cpp) + auto s = makeString(testStrdupPrintfValist("\n-%s-%s-%s-\n", "\r", "\t", "\b")); + EXPECT_EQ("\n-\r-\t-\b-\n", s); +} + +TEST_F(UtilsTest, trStrdupPrintfFmtS) +{ + auto s = makeString(tr_strdup_printf("%s", "test")); + EXPECT_EQ("test", s); +} + +TEST_F(UtilsTest, trStrdupPrintf) +{ + auto s = makeString(tr_strdup_printf("%d %s %c %u", -1, "0", '1', 2)); + EXPECT_EQ("-1 0 1 2", s); + + auto* s3 = reinterpret_cast(tr_malloc0(4098)); + memset(s3, '-', 4097); + s3[2047] = 't'; + s3[2048] = 'e'; + s3[2049] = 's'; + s3[2050] = 't'; + + auto* s2 = reinterpret_cast(tr_malloc0(4096)); + memset(s2, '-', 4095); + s2[2047] = '%'; + s2[2048] = 's'; + + // NOLINTNEXTLINE(clang-diagnostic-format-nonliteral) + s = makeString(tr_strdup_printf(s2, "test")); + EXPECT_EQ(s3, s); + + tr_free(s2); + + s = makeString(tr_strdup_printf("%s", s3)); + EXPECT_EQ(s3, s); + + tr_free(s3); +} + +TEST_F(UtilsTest, env) +{ + char const* test_key = "TR_TEST_ENV"; + + unsetenv(test_key); + + EXPECT_FALSE(tr_env_key_exists(test_key)); + EXPECT_EQ(123, tr_env_get_int(test_key, 123)); + EXPECT_EQ(nullptr, tr_env_get_string(test_key, nullptr)); + auto s = makeString(tr_env_get_string(test_key, "a")); + EXPECT_EQ("a", s); + + setenv(test_key, "", 1); + + EXPECT_TRUE(tr_env_key_exists(test_key)); + EXPECT_EQ(456, tr_env_get_int(test_key, 456)); + s = makeString(tr_env_get_string(test_key, nullptr)); + EXPECT_EQ("", s); + s = makeString(tr_env_get_string(test_key, "b")); + EXPECT_EQ("", s); + + setenv(test_key, "135", 1); + + EXPECT_TRUE(tr_env_key_exists(test_key)); + EXPECT_EQ(135, tr_env_get_int(test_key, 789)); + s = makeString(tr_env_get_string(test_key, nullptr)); + EXPECT_EQ("135", s); + s = makeString(tr_env_get_string(test_key, "c")); + EXPECT_EQ("135", s); +} diff --git a/tests/libtransmission/variant-test.cc b/tests/libtransmission/variant-test.cc new file mode 100644 index 000000000..ec031684b --- /dev/null +++ b/tests/libtransmission/variant-test.cc @@ -0,0 +1,551 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#define LIBTRANSMISSION_VARIANT_MODULE + +#include "transmission.h" +#include "utils.h" /* tr_free */ +#include "variant-common.h" +#include "variant.h" + +#include +#include +#include // lrint() +#include // isspace() +#include + +#include "gtest/gtest.h" + +class VariantTest : public ::testing::Test +{ +protected: + std::string stripWhitespace(std::string const& in) + { + auto s = in; + s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), ::isspace)); + s.erase(std::find_if_not(s.rbegin(), s.rend(), ::isspace).base(), s.end()); + return s; + } + + auto bencParseInt(std::string const& in, uint8_t const** end, int64_t* val) + { + return tr_bencParseInt(in.data(), + in.data() + in.size(), + end, val); + } +}; + +#ifndef _WIN32 +# define STACK_SMASH_DEPTH (1 * 1000 * 1000) +#else +# define STACK_SMASH_DEPTH (100 * 1000) +#endif + +TEST_F(VariantTest, parseInt) +{ + auto const in = std::string { "i64e" }; + auto constexpr InitVal = int64_t { 888 }; + auto constexpr ExpectVal = int64_t { 64 }; + + uint8_t const* end = {}; + auto val = int64_t { InitVal }; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(0, err); + EXPECT_EQ(ExpectVal, val); + EXPECT_EQ(reinterpret_cast(in.data() + in.size()), end); +} + +TEST_F(VariantTest, parseIntWithMissingEnd) +{ + auto const in = std::string { "i64" }; + auto constexpr InitVal = int64_t { 888 }; + + uint8_t const* end = {}; + auto val = int64_t { InitVal }; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(EILSEQ, err); + EXPECT_EQ(InitVal, val); + EXPECT_EQ(nullptr, end); +} + +TEST_F(VariantTest, parseIntEmptyBuffer) +{ + auto const in = std::string {}; + auto constexpr InitVal = int64_t { 888 }; + + uint8_t const* end = {}; + auto val = int64_t { InitVal }; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(EILSEQ, err); + EXPECT_EQ(InitVal, val); + EXPECT_EQ(nullptr, end); +} + +TEST_F(VariantTest, parseIntWithBadDigits) +{ + auto const in = std::string { "i6z4e" }; + auto constexpr InitVal = int64_t { 888 }; + + uint8_t const* end = {}; + auto val = int64_t { InitVal }; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(EILSEQ, err); + EXPECT_EQ(InitVal, val); + EXPECT_EQ(nullptr, end); +} + +TEST_F(VariantTest, parseNegativeInt) +{ + auto const in = std::string { "i-3e" }; + + uint8_t const* end = {}; + auto val = int64_t {}; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(0, err); + EXPECT_EQ(-3, val); + EXPECT_EQ(reinterpret_cast(in.data() + in.size()), end); +} + +TEST_F(VariantTest, parseIntZero) +{ + auto const in = std::string { "i0e" }; + + uint8_t const* end = {}; + auto val = int64_t {}; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(0, err); + EXPECT_EQ(0, val); + EXPECT_EQ(reinterpret_cast(in.data() + in.size()), end); +} + +TEST_F(VariantTest, parseIntWithLeadingZero) +{ + auto const in = std::string { "i04e" }; + auto constexpr InitVal = int64_t { 888 }; + + uint8_t const* end = {}; + auto val = int64_t { InitVal }; + auto const err = bencParseInt(in, &end, &val); + EXPECT_EQ(EILSEQ, err); // no leading zeroes allowed + EXPECT_EQ(InitVal, val); + EXPECT_EQ(nullptr, end); +} + +TEST_F(VariantTest, str) +{ + auto buf = std::array{}; + int err; + int n; + uint8_t const* end; + uint8_t const* str; + size_t len; + + // string len is designed to overflow + n = tr_snprintf(buf.data(), buf.size(), "%zu:boat", size_t(SIZE_MAX - 2)); + err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len); + EXPECT_EQ(EILSEQ, err); + EXPECT_EQ(size_t{}, len); + EXPECT_EQ(nullptr, str); + EXPECT_EQ(nullptr, end); + + // good string + n = tr_snprintf(buf.data(), buf.size(), "4:boat"); + err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len); + EXPECT_EQ(0, err); + EXPECT_EQ(size_t{ 4 }, len); + EXPECT_EQ(0, memcmp("boat", str, len)); + EXPECT_EQ(buf.data() + n, end); + str = nullptr; + end = nullptr; + len = 0; + + // string goes past end of buffer + err = tr_bencParseStr(&buf[0], &buf[n - 1], &end, &str, &len); + EXPECT_EQ(EILSEQ, err); + EXPECT_EQ(size_t{}, len); + EXPECT_EQ(nullptr, str); + EXPECT_EQ(nullptr, end); + + // empty string + n = tr_snprintf(buf.data(), buf.size(), "0:"); + err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len); + EXPECT_EQ(0, err); + EXPECT_EQ(size_t{}, len); + EXPECT_EQ('\0', *str); + EXPECT_EQ(buf.data() + n, end); + str = nullptr; + end = nullptr; + len = 0; + + // short string + n = tr_snprintf(buf.data(), buf.size(), "3:boat"); + err = tr_bencParseStr(&buf[0], &buf[n], &end, &str, &len); + EXPECT_EQ(0, err); + EXPECT_EQ(size_t{ 3 }, len); + EXPECT_EQ(0, memcmp("boa", str, len)); + EXPECT_EQ(buf.data() + 5, end); + str = nullptr; + end = nullptr; + len = 0; +} + +TEST_F(VariantTest, parse) +{ + auto buf = std::array{}; + int64_t i; + + tr_variant val; + char const* end; + auto n = tr_snprintf(buf.data(), buf.size(), "i64e"); + auto err = tr_variantFromBencFull(&val, buf.data(), n, nullptr, &end); + EXPECT_EQ(0, err); + EXPECT_TRUE(tr_variantGetInt(&val, &i)); + EXPECT_EQ(int64_t(64), i); + EXPECT_EQ(reinterpret_cast(buf.data()) + n, end); + tr_variantFree(&val); + + n = tr_snprintf(buf.data(), buf.size(), "li64ei32ei16ee"); + err = tr_variantFromBencFull(&val, buf.data(), n, nullptr, &end); + EXPECT_EQ(0, err); + EXPECT_EQ(reinterpret_cast(&buf[n]), end); + EXPECT_EQ(size_t{ 3 }, tr_variantListSize(&val)); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&val, 0), &i)); + EXPECT_EQ(64, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&val, 1), &i)); + EXPECT_EQ(32, i); + EXPECT_TRUE(tr_variantGetInt(tr_variantListChild(&val, 2), &i)); + EXPECT_EQ(16, i); + + size_t len; + auto* saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); + EXPECT_EQ(static_cast(n), len); + EXPECT_STREQ(reinterpret_cast(buf.data()), saved); + tr_free(saved); + + tr_variantFree(&val); + end = nullptr; + + n = tr_snprintf(buf.data(), buf.size(), "lllee"); + err = tr_variantFromBencFull(&val, buf.data(), n, nullptr, &end); + EXPECT_NE(0, err); + EXPECT_EQ(nullptr, end); + + end = nullptr; + n = tr_snprintf(buf.data(), buf.size(), "le"); + err = tr_variantFromBencFull(&val, buf.data(), n, nullptr, &end); + EXPECT_EQ(0, err); + EXPECT_EQ(reinterpret_cast(&buf[n]), end); + + saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); + EXPECT_EQ(static_cast(n), len); + EXPECT_STREQ("le", saved); + tr_free(saved); + tr_variantFree(&val); +} + +TEST_F(VariantTest, bencParseAndReencode) { + struct Test + { + std::string benc; + bool is_good; + }; + + auto const tests = std::array{ + Test{ "llleee", true }, + { "d3:cow3:moo4:spam4:eggse", true }, + { "d4:spaml1:a1:bee", true }, + { "d5:greenli1ei2ei3ee4:spamd1:ai123e3:keyi214eee", true }, + { "d9:publisher3:bob17:publisher-webpage15:www.example.com18:publisher.location4:homee", true }, + { "d8:completei1e8:intervali1800e12:min intervali1800e5:peers0:e", true }, + { "d1:ai0e1:be", false }, // odd number of children + { "", false }, + { " ", false } + }; + + for (const auto& test : tests) + { + tr_variant val; + char const* end = nullptr; + auto const err = tr_variantFromBencFull(&val, test.benc.data(), test.benc.size(), nullptr, &end); + if (!test.is_good) + { + EXPECT_NE(0, err); + } + else + { + EXPECT_EQ(0, err); + EXPECT_EQ(test.benc.data() + test.benc.size(), end); + auto saved_len = size_t{}; + auto* saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &saved_len); + EXPECT_EQ(test.benc, std::string(saved, saved_len)); + tr_free(saved); + tr_variantFree(&val); + } + } +} + +TEST_F(VariantTest, bencSortWhenSerializing) +{ + auto const in = std::string { "lld1:bi32e1:ai64eeee" }; + auto const expected_out = std::string { "lld1:ai64e1:bi32eeee" }; + + tr_variant val; + char const* end; + auto const err = tr_variantFromBencFull(&val, in.data(), in.size(), nullptr, &end); + EXPECT_EQ(0, err); + EXPECT_EQ(reinterpret_cast(in.data() + in.size()), end); + + auto len = size_t{}; + auto* saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); + EXPECT_EQ(expected_out, std::string(saved, len)); + tr_free(saved); + + tr_variantFree(&val); +} + +TEST_F(VariantTest, bencMalformedTooManyEndings) +{ + auto const in = std::string { "leee" }; + auto const expected_out = std::string { "le" }; + + tr_variant val; + char const* end; + auto const err = tr_variantFromBencFull(&val, in.data(), in.size(), nullptr, &end); + EXPECT_EQ(0, err); + EXPECT_EQ(in.data() + expected_out.size(), end); + + auto len = size_t{}; + auto* saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); + EXPECT_EQ(expected_out, std::string(saved, len)); + tr_free(saved); + + tr_variantFree(&val); +} + +TEST_F(VariantTest, bencMalformedNoEnding) +{ + auto const in = std::string { "l1:a1:b1:c" }; + tr_variant val; + EXPECT_EQ(EILSEQ, tr_variantFromBenc(&val, in.data(), in.size())); +} + +TEST_F(VariantTest, bencMalformedIncompleteString) +{ + auto const in = std::string { "1:" }; + tr_variant val; + EXPECT_EQ(EILSEQ, tr_variantFromBenc(&val, in.data(), in.size())); +} + +TEST_F(VariantTest, bencToJson) +{ + struct Test + { + std::string benc; + std::string expected; + }; + + auto const tests = std::array{ + Test{ "i6e", "6" }, + { "d5:helloi1e5:worldi2ee", R"({"hello":1,"world":2})" }, + { "d5:helloi1e5:worldi2e3:fooli1ei2ei3eee", R"({"foo":[1,2,3],"hello":1,"world":2})" }, + { "d5:helloi1e5:worldi2e3:fooli1ei2ei3ed1:ai0eeee", R"({"foo":[1,2,3,{"a":0}],"hello":1,"world":2})" }, + { "d4:argsd6:statusle7:status2lee6:result7:successe", R"({"args":{"status":[],"status2":[]},"result":"success"})" } + }; + + for (auto const& test : tests) + { + tr_variant top; + tr_variantFromBenc(&top, test.benc.data(), test.benc.size()); + + auto len = size_t{}; + auto* str = tr_variantToStr(&top, TR_VARIANT_FMT_JSON_LEAN, &len); + EXPECT_EQ(test.expected, stripWhitespace(std::string(str, len))); + tr_free(str); + tr_variantFree(&top); + } +} + +TEST_F(VariantTest, merge) +{ + auto const i1 = tr_quark_new("i1", 2); + auto const i2 = tr_quark_new("i2", 2); + auto const i3 = tr_quark_new("i3", 2); + auto const i4 = tr_quark_new("i4", 2); + auto const s5 = tr_quark_new("s5", 2); + auto const s6 = tr_quark_new("s6", 2); + auto const s7 = tr_quark_new("s7", 2); + auto const s8 = tr_quark_new("s8", 2); + + /* initial dictionary (default values) */ + tr_variant dest; + tr_variantInitDict(&dest, 10); + tr_variantDictAddInt(&dest, i1, 1); + tr_variantDictAddInt(&dest, i2, 2); + tr_variantDictAddInt(&dest, i4, -35); /* remains untouched */ + tr_variantDictAddStr(&dest, s5, "abc"); + tr_variantDictAddStr(&dest, s6, "def"); + tr_variantDictAddStr(&dest, s7, "127.0.0.1"); /* remains untouched */ + + /* new dictionary, will overwrite items in dest */ + tr_variant src; + tr_variantInitDict(&src, 10); + tr_variantDictAddInt(&src, i1, 1); /* same value */ + tr_variantDictAddInt(&src, i2, 4); /* new value */ + tr_variantDictAddInt(&src, i3, 3); /* new key:value */ + tr_variantDictAddStr(&src, s5, "abc"); /* same value */ + tr_variantDictAddStr(&src, s6, "xyz"); /* new value */ + tr_variantDictAddStr(&src, s8, "ghi"); /* new key:value */ + + tr_variantMergeDicts(&dest, /*const*/ &src); + + int64_t i; + EXPECT_TRUE(tr_variantDictFindInt(&dest, i1, &i)); + EXPECT_EQ(1, i); + EXPECT_TRUE(tr_variantDictFindInt(&dest, i2, &i)); + EXPECT_EQ(4, i); + EXPECT_TRUE(tr_variantDictFindInt(&dest, i3, &i)); + EXPECT_EQ(3, i); + EXPECT_TRUE(tr_variantDictFindInt(&dest, i4, &i)); + EXPECT_EQ(-35, i); + size_t len; + char const* s; + EXPECT_TRUE(tr_variantDictFindStr(&dest, s5, &s, &len)); + EXPECT_EQ(size_t{ 3 }, len); + EXPECT_STREQ("abc", s); + EXPECT_TRUE(tr_variantDictFindStr(&dest, s6, &s, &len)); + EXPECT_EQ(size_t{ 3 }, len); + EXPECT_STREQ("xyz", s); + EXPECT_TRUE(tr_variantDictFindStr(&dest, s7, &s, &len)); + EXPECT_EQ(size_t{ 9 }, len); + EXPECT_STREQ("127.0.0.1", s); + EXPECT_TRUE(tr_variantDictFindStr(&dest, s8, &s, &len)); + EXPECT_EQ(size_t{ 3 }, len); + EXPECT_STREQ("ghi", s); + + tr_variantFree(&dest); + tr_variantFree(&src); +} + +TEST_F(VariantTest, stackSmash) +{ + // make a nested list of list of lists. + int constexpr Depth = STACK_SMASH_DEPTH; + std::string const in = std::string(Depth, 'l') + std::string(Depth, 'e'); + + // confirm that it parses + char const* end; + tr_variant val; + auto err = tr_variantFromBencFull(&val, in.data(), in.size(), nullptr, &end); + EXPECT_EQ(0, err); + EXPECT_EQ(in.data() + in.size(), end); + + // confirm that we can serialize it back again + size_t len; + auto* saved = tr_variantToStr(&val, TR_VARIANT_FMT_BENC, &len); + EXPECT_EQ(in, std::string(saved, len)); + tr_free(saved); + + tr_variantFree(&val); +} + +TEST_F(VariantTest, boolAndIntRecast) +{ + auto const key1 = tr_quark_new("key1", 4); + auto const key2 = tr_quark_new("key2", 4); + auto const key3 = tr_quark_new("key3", 4); + auto const key4 = tr_quark_new("key4", 4); + + tr_variant top; + tr_variantInitDict(&top, 10); + tr_variantDictAddBool(&top, key1, false); + tr_variantDictAddBool(&top, key2, 0); // NOLINT modernize-use-bool-literals + tr_variantDictAddInt(&top, key3, true); + tr_variantDictAddInt(&top, key4, 1); + + // confirm we can read both bools and ints as bools + bool b; + EXPECT_TRUE(tr_variantDictFindBool(&top, key1, &b)); + EXPECT_FALSE(b); + EXPECT_TRUE(tr_variantDictFindBool(&top, key2, &b)); + EXPECT_FALSE(b); + EXPECT_TRUE(tr_variantDictFindBool(&top, key3, &b)); + EXPECT_TRUE(b); + EXPECT_TRUE(tr_variantDictFindBool(&top, key4, &b)); + EXPECT_TRUE(b); + + // confirm we can read both bools and ints as ints + int64_t i; + EXPECT_TRUE(tr_variantDictFindInt(&top, key1, &i)); + EXPECT_EQ(0, i); + EXPECT_TRUE(tr_variantDictFindInt(&top, key2, &i)); + EXPECT_EQ(0, i); + EXPECT_TRUE(tr_variantDictFindInt(&top, key3, &i)); + EXPECT_NE(0, i); + EXPECT_TRUE(tr_variantDictFindInt(&top, key4, &i)); + EXPECT_NE(0, i); + + tr_variantFree(&top); +} + +TEST_F(VariantTest, dictFindType) +{ + auto const expected_str = std::string { "this-is-a-string" }; + auto const expected_bool = bool{ true }; + auto const expected_int = int{ 1234 }; + auto const expected_real = double{ 0.3 }; + + auto const key_bool = tr_quark_new("this-is-a-bool", TR_BAD_SIZE); + auto const key_real = tr_quark_new("this-is-a-real", TR_BAD_SIZE); + auto const key_int = tr_quark_new("this-is-an-int", TR_BAD_SIZE); + auto const key_str = tr_quark_new("this-is-a-string", TR_BAD_SIZE); + + // populate a dict + tr_variant top; + tr_variantInitDict(&top, 0); + tr_variantDictAddBool(&top, key_bool, expected_bool); + tr_variantDictAddInt(&top, key_int, expected_int); + tr_variantDictAddReal(&top, key_real, expected_real); + tr_variantDictAddStr(&top, key_str, expected_str.data()); + + // look up the keys as strings + char const* str = {}; + auto len = size_t{}; + EXPECT_FALSE(tr_variantDictFindStr(&top, key_bool, &str, &len)); + EXPECT_FALSE(tr_variantDictFindStr(&top, key_real, &str, &len)); + EXPECT_FALSE(tr_variantDictFindStr(&top, key_int, &str, &len)); + EXPECT_TRUE(tr_variantDictFindStr(&top, key_str, &str, &len)); + EXPECT_EQ(expected_str, std::string(str, len)); + + // look up the keys as bools + auto b = bool{}; + EXPECT_FALSE(tr_variantDictFindBool(&top, key_int, &b)); + EXPECT_FALSE(tr_variantDictFindBool(&top, key_real, &b)); + EXPECT_FALSE(tr_variantDictFindBool(&top, key_str, &b)); + EXPECT_TRUE(tr_variantDictFindBool(&top, key_bool, &b)); + EXPECT_EQ(expected_bool, b); + + // look up the keys as doubles + auto d = double{}; + EXPECT_FALSE(tr_variantDictFindReal(&top, key_bool, &d)); + EXPECT_TRUE(tr_variantDictFindReal(&top, key_int, &d)); + EXPECT_EQ(expected_int, std::lrint(d)); + EXPECT_FALSE(tr_variantDictFindReal(&top, key_str, &d)); + EXPECT_TRUE(tr_variantDictFindReal(&top, key_real, &d)); + EXPECT_EQ(std::lrint(expected_real * 100), std::lrint(d * 100)); + + // look up the keys as ints + auto i = int64_t {}; + EXPECT_TRUE(tr_variantDictFindInt(&top, key_bool, &i)); + EXPECT_EQ(expected_bool ? 1 : 0, i); + EXPECT_FALSE(tr_variantDictFindInt(&top, key_real, &i)); + EXPECT_FALSE(tr_variantDictFindInt(&top, key_str, &i)); + EXPECT_TRUE(tr_variantDictFindInt(&top, key_int, &i)); + EXPECT_EQ(expected_int, i); + + tr_variantFree(&top); +} diff --git a/tests/libtransmission/watchdir-test.cc b/tests/libtransmission/watchdir-test.cc new file mode 100644 index 000000000..1acb319fb --- /dev/null +++ b/tests/libtransmission/watchdir-test.cc @@ -0,0 +1,374 @@ +/* + * This file Copyright (C) 2015-2016 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include "transmission.h" +#include "file.h" +#include "net.h" +#include "utils.h" +#include "watchdir.h" + +#include "test-fixtures.h" + +#include + +#include +#include + +/*** +**** +***/ + +extern "C" +{ +extern struct timeval tr_watchdir_generic_interval; +extern unsigned int tr_watchdir_retry_limit; +extern struct timeval tr_watchdir_retry_start_interval; +extern struct timeval tr_watchdir_retry_max_interval; +} + +namespace +{ + +auto constexpr FiftyMsec = timeval { 0, 50000 }; +auto constexpr OneHundredMsec = timeval { 0, 100000 }; +auto constexpr TwoHundredMsec = timeval { 0, 200000 }; + +} + +namespace libtransmission +{ + +namespace test +{ + +enum class WatchMode +{ + NATIVE, + GENERIC +}; + +class WatchDirTest : + public SandboxedTest, + public ::testing::WithParamInterface +{ +private: + std::shared_ptr ev_base_; + +protected: + void SetUp() override + { + SandboxedTest::SetUp(); + ev_base_.reset(event_base_new(), event_base_free); + + // speed up generic implementation + tr_watchdir_generic_interval = OneHundredMsec; + } + + void TearDown() override + { + ev_base_.reset(); + + SandboxedTest::TearDown(); + } + + auto createWatchDir(std::string const& path, tr_watchdir_cb cb, void* cb_data) + { + auto const force_generic = GetParam() == WatchMode::GENERIC; + return tr_watchdir_new(path.c_str(), cb, cb_data, ev_base_.get(), force_generic); + } + + std::string createFile(std::string const& parent_dir, std::string const& name) + { + auto path = parent_dir; + path += TR_PATH_DELIMITER; + path += name; + + createFileWithContents(path, ""); + + return path; + } + + std::string createDir(std::string const& parent_dir, std::string const& name) + { + auto path = parent_dir; + path += TR_PATH_DELIMITER; + path += name; + + tr_sys_dir_create(path.c_str(), 0, 0700, nullptr); + + return path; + } + + void processEvents() + { + event_base_loopexit(ev_base_.get(), &TwoHundredMsec); + event_base_dispatch(ev_base_.get()); + } + + struct CallbackData + { + explicit CallbackData(tr_watchdir_status status = TR_WATCHDIR_ACCEPT) : + result{status} {} + tr_watchdir_status result {}; + + tr_watchdir_t wd = {}; + std::string name = {}; + }; + + static tr_watchdir_status callback(tr_watchdir_t wd, char const* name, void* vdata) noexcept + { + auto* data = static_cast(vdata); + auto const result = data->result; + + if (result != TR_WATCHDIR_RETRY) + { + data->wd = wd; + data->name = name; + } + + return result; + } +}; + +TEST_P(WatchDirTest, construct) +{ + auto const path = sandboxDir(); + + auto wd = createWatchDir(path, &callback, nullptr); + EXPECT_NE(nullptr, wd); + EXPECT_TRUE(tr_sys_path_is_same(path.c_str(), tr_watchdir_get_path(wd), nullptr)); + + processEvents(); + + tr_watchdir_free(wd); +} + +TEST_P(WatchDirTest, initialScan) +{ + auto const path = sandboxDir(); + + // setup: start with an empty directory. + // this block confirms that it's empty + { + auto wd_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto wd = createWatchDir(path, &callback, &wd_data); + EXPECT_NE(nullptr, wd); + + processEvents(); + EXPECT_EQ(nullptr, wd_data.wd); + EXPECT_EQ("", wd_data.name); + + tr_watchdir_free(wd); + } + + // add a file + auto const base_name = std::string { "test.txt" }; + createFile(path, base_name); + + // confirm that a wd will pick up the file that + // was created before the wd was instantiated + { + auto wd_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto wd = createWatchDir(path, &callback, &wd_data); + EXPECT_NE(nullptr, wd); + + processEvents(); + EXPECT_EQ(wd, wd_data.wd); + EXPECT_EQ(base_name, wd_data.name); + + tr_watchdir_free(wd); + } +} + +TEST_P(WatchDirTest, watch) +{ + auto const path = sandboxDir(); + + // create a new watchdir and confirm it's empty + auto wd_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto wd = createWatchDir(path, &callback, &wd_data); + EXPECT_NE(nullptr, wd); + processEvents(); + EXPECT_EQ(nullptr, wd_data.wd); + EXPECT_EQ("", wd_data.name); + + // test that a new file in an empty directory shows up + auto const file1 = std::string { "test1" }; + createFile(path, file1); + processEvents(); + EXPECT_EQ(wd, wd_data.wd); + EXPECT_EQ(file1, wd_data.name); + + // test that a new file in a nonempty directory shows up + wd_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const file2 = std::string { "test2" }; + createFile(path, file2); + processEvents(); + EXPECT_EQ(wd, wd_data.wd); + EXPECT_EQ(file2, wd_data.name); + + // test that folders don't trigger the callback + wd_data = CallbackData(TR_WATCHDIR_ACCEPT); + createDir(path, "test3"); + processEvents(); + EXPECT_EQ(nullptr, wd_data.wd); + EXPECT_EQ("", wd_data.name); + + // cleanup + tr_watchdir_free(wd); +} + +TEST_P(WatchDirTest, watchTwoDirs) +{ + auto top = sandboxDir(); + + // create two empty directories and watch them + auto wd1_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const dir1 = createDir(top, "a"); + auto wd1 = createWatchDir(dir1, &callback, &wd1_data); + EXPECT_NE(wd1, nullptr); + auto wd2_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const dir2 = createDir(top, "b"); + auto wd2 = createWatchDir(dir2, &callback, &wd2_data); + EXPECT_NE(wd2, nullptr); + + processEvents(); + EXPECT_EQ(nullptr, wd1_data.wd); + EXPECT_EQ("", wd1_data.name); + EXPECT_EQ(nullptr, wd2_data.wd); + EXPECT_EQ("", wd2_data.name); + + // add a file into directory 1 and confirm it triggers + // a callback with the right wd + auto const file1 = std::string { "test.txt" }; + createFile(dir1, file1); + processEvents(); + EXPECT_EQ(wd1, wd1_data.wd); + EXPECT_EQ(file1, wd1_data.name); + EXPECT_EQ(nullptr, wd2_data.wd); + EXPECT_EQ("", wd2_data.name); + + // add a file into directory 2 and confirm it triggers + // a callback with the right wd + wd1_data = CallbackData(TR_WATCHDIR_ACCEPT); + wd2_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const file2 = std::string { "test2.txt" }; + createFile(dir2, file2); + processEvents(); + EXPECT_EQ(nullptr, wd1_data.wd); + EXPECT_EQ("", wd1_data.name); + EXPECT_EQ(wd2, wd2_data.wd); + EXPECT_EQ(file2, wd2_data.name); + + // TODO(ckerr): watchdir.c seems to treat IGNORE and ACCEPT identically + // so I'm not sure what's intended or what this is supposed to + // be testing. + wd1_data = CallbackData(TR_WATCHDIR_IGNORE); + wd2_data = CallbackData(TR_WATCHDIR_IGNORE); + auto const file3 = std::string { "test3.txt" }; + auto const file4 = std::string { "test4.txt" }; + createFile(dir1, file3); + createFile(dir2, file4); + processEvents(); + EXPECT_EQ(wd1, wd1_data.wd); + EXPECT_EQ(file3, wd1_data.name); + EXPECT_EQ(wd2, wd2_data.wd); + EXPECT_EQ(file4, wd2_data.name); + + // confirm that callbacks don't get confused + // when there's a new file in directory 'a' + // and a new directory in directory 'b' + wd1_data = CallbackData(TR_WATCHDIR_ACCEPT); + wd2_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const file5 = std::string { "test5.txt" }; + createFile(dir1, file5); + createDir(dir2, file5); + processEvents(); + EXPECT_EQ(wd1, wd1_data.wd); + EXPECT_EQ(file5, wd1_data.name); + EXPECT_EQ(nullptr, wd2_data.wd); + EXPECT_EQ("", wd2_data.name); + + // reverse the order of the previous test: + // confirm that callbacks don't get confused + // when there's a new file in directory 'b' + // and a new directory in directory 'a' + wd1_data = CallbackData(TR_WATCHDIR_ACCEPT); + wd2_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const file6 = std::string { "test6.txt" }; + createDir(dir1, file6); + createFile(dir2, file6); + processEvents(); + EXPECT_EQ(nullptr, wd1_data.wd); + EXPECT_EQ("", wd1_data.name); + EXPECT_EQ(wd2, wd2_data.wd); + EXPECT_EQ(file6, wd2_data.name); + + // confirm that creating new directories in BOTH + // watchdirs still triggers no callbacks + wd1_data = CallbackData(TR_WATCHDIR_ACCEPT); + wd2_data = CallbackData(TR_WATCHDIR_ACCEPT); + auto const file7 = std::string { "test7.txt" }; + auto const file8 = std::string { "test8.txt" }; + createDir(dir1, file7); + createDir(dir2, file8); + processEvents(); + EXPECT_EQ(nullptr, wd1_data.wd); + EXPECT_EQ("", wd1_data.name); + EXPECT_EQ(nullptr, wd2_data.wd); + EXPECT_EQ("", wd2_data.name); + + // cleanup + tr_watchdir_free(wd2); + tr_watchdir_free(wd1); +} + +TEST_P(WatchDirTest, retry) +{ + auto const path = sandboxDir(); + + // tune retry logic + tr_watchdir_retry_limit = 10; + tr_watchdir_retry_start_interval = FiftyMsec; + tr_watchdir_retry_max_interval = tr_watchdir_retry_start_interval; + + // test setup: + // Start watching the test directory. + // Create a file and return 'retry' back to the watchdir code + // from our callback. This should cause the wd to wait a bit + // and try again. + auto wd_data = CallbackData(TR_WATCHDIR_RETRY); + auto wd = createWatchDir(path, &callback, &wd_data); + EXPECT_NE(nullptr, wd); + processEvents(); + EXPECT_EQ(nullptr, wd_data.wd); + EXPECT_EQ("", wd_data.name); + + auto const test_file = std::string { "test" }; + createFile(path, test_file); + processEvents(); + EXPECT_EQ(nullptr, wd_data.wd); + EXPECT_EQ("", wd_data.name); + + // confirm that wd retries. + // return 'accept' in the callback so it won't keep retrying. + wd_data = CallbackData(TR_WATCHDIR_ACCEPT); + processEvents(); + EXPECT_EQ(wd, wd_data.wd); + EXPECT_EQ(test_file, wd_data.name); +} + +INSTANTIATE_TEST_SUITE_P( + WatchDir, + WatchDirTest, + ::testing::Values(WatchMode::NATIVE, WatchMode::GENERIC) + ); + +} // namespace test + +} // namespace libtransmission diff --git a/third-party/Makefile.am b/third-party/Makefile.am index 33df96d42..30137824a 100644 --- a/third-party/Makefile.am +++ b/third-party/Makefile.am @@ -23,6 +23,7 @@ SUBDIRS = \ EXTRA_DIST = \ curl \ + googletest \ libevent \ openssl \ macosx-libevent-config.h \ diff --git a/third-party/googletest b/third-party/googletest new file mode 160000 index 000000000..68ca04c26 --- /dev/null +++ b/third-party/googletest @@ -0,0 +1 @@ +Subproject commit 68ca04c261ded1b936ef5c121618247f7010d445 diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 6452707f5..c37c9ba68 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -1,6 +1,15 @@ project(trutils) -include_directories(${CMAKE_SOURCE_DIR}) +add_compile_options(${C_WARNING_FLAGS}) + +include_directories( + ${CMAKE_SOURCE_DIR} +) +include_directories( + SYSTEM + ${EVENT2_INCLUDE_DIRS} + ${CURL_INCLUDE_DIRS} +) foreach(P create edit remote show) tr_win32_app_info(${PROJECT_NAME}_${P}_WIN32_RC_FILE @@ -9,7 +18,6 @@ foreach(P create edit remote show) "${TR_NAME}-${P}.exe") add_executable(${TR_NAME}-${P} ${P}.c ${${PROJECT_NAME}_${P}_WIN32_RC_FILE}) - include_directories(${TR_NAME}-${P} ${EVENT2_INCLUDE_DIRS} ${CURL_INCLUDE_DIRS}) target_link_libraries(${TR_NAME}-${P} ${TR_NAME}) install(TARGETS ${TR_NAME}-${P} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/utils/edit.c b/utils/edit.c index b5284da20..161db9f96 100644 --- a/utils/edit.c +++ b/utils/edit.c @@ -379,6 +379,6 @@ int tr_main(int argc, char* argv[]) printf("Changed %d files\n", changedCount); - tr_free(files); + tr_free((void*)files); return EXIT_SUCCESS; } diff --git a/utils/remote.c b/utils/remote.c index 7af3ed33c..0cf098890 100644 --- a/utils/remote.c +++ b/utils/remote.c @@ -16,7 +16,6 @@ #include #include -#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */ #include #include @@ -38,21 +37,18 @@ #define ARGUMENTS TR_KEY_arguments #define MEM_K 1024 -#define MEM_B_STR "B" #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_B_STR "B" #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_B_STR "B/s" #define SPEED_K_STR "kB/s" #define SPEED_M_STR "MB/s" #define SPEED_G_STR "GB/s" @@ -968,12 +964,11 @@ static void printDetails(tr_variant* top) if (tr_variantDictFindList(t, TR_KEY_labels, &l)) { - int const n = tr_variantListSize(l); - char const* str; - printf(" Labels: "); - for (int i = 0; i < n; i++) + size_t child_pos = 0; + tr_variant* child; + while ((child = tr_variantListChild(l, child_pos++))) { - if (tr_variantGetStr(tr_variantListChild(l, i), &str, NULL)) + if (tr_variantGetStr(child, &str, NULL)) { printf(i == 0 ? "%s" : ", %s", str); } @@ -1707,11 +1702,13 @@ static void printTrackers(tr_variant* top) static void printSession(tr_variant* top) { tr_variant* args; + char buf[128]; + char buf2[128]; + char buf3[128]; if (tr_variantDictFindDict(top, TR_KEY_arguments, &args)) { int64_t i; - char buf[64]; bool boolVal; char const* str; @@ -1819,10 +1816,6 @@ static void printSession(tr_variant* top) tr_variantDictFindReal(args, TR_KEY_seedRatioLimit, &seedRatioLimit) && tr_variantDictFindBool(args, TR_KEY_seedRatioLimited, &seedRatioLimited)) { - char buf[128]; - char buf2[128]; - char buf3[128]; - printf("LIMITS\n"); printf(" Peer limit: %" PRId64 "\n", peerLimit); diff --git a/utils/show.c b/utils/show.c index e262788d8..dee652c05 100644 --- a/utils/show.c +++ b/utils/show.c @@ -11,7 +11,7 @@ #include /* qsort() */ #include -#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */ +// #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */ #include #include @@ -279,11 +279,11 @@ static void doScrape(tr_info const* inf) { if (tr_variantDictFindDict(&top, TR_KEY_files, &files)) { - int i = 0; + size_t child_pos = 0; tr_quark key; tr_variant* val; - while (tr_variantDictChild(files, i, &key, &val)) + while (tr_variantDictChild(files, child_pos, &key, &val)) { if (memcmp(inf->hash, tr_quark_get_string(key, NULL), SHA_DIGEST_LENGTH) == 0) { @@ -303,7 +303,7 @@ static void doScrape(tr_info const* inf) matched = true; } - ++i; + ++child_pos; } }